Merge pull request #3811 from chaunceyjiang/label_selector
feat: Introduce a LabelSelector field to DependentObjectReference
This commit is contained in:
commit
8680ff333f
|
@ -153,6 +153,13 @@ type DependentObjectReference struct {
|
||||||
Namespace string `json:"namespace,omitempty"`
|
Namespace string `json:"namespace,omitempty"`
|
||||||
|
|
||||||
// Name represents the name of the referent.
|
// Name represents the name of the referent.
|
||||||
// +required
|
// Name and LabelSelector cannot be empty at the same time.
|
||||||
Name string `json:"name"`
|
// +optional
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// LabelSelector represents a label query over a set of resources.
|
||||||
|
// If name is not empty, labelSelector will be ignored.
|
||||||
|
// Name and LabelSelector cannot be empty at the same time.
|
||||||
|
// +optional
|
||||||
|
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
v1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
v1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -96,6 +97,11 @@ func (in *DependencyInterpretation) DeepCopy() *DependencyInterpretation {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *DependentObjectReference) DeepCopyInto(out *DependentObjectReference) {
|
func (in *DependentObjectReference) DeepCopyInto(out *DependentObjectReference) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.LabelSelector != nil {
|
||||||
|
in, out := &in.LabelSelector, &out.LabelSelector
|
||||||
|
*out = new(v1.LabelSelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,7 +374,9 @@ func (in *ResourceInterpreterResponse) DeepCopyInto(out *ResourceInterpreterResp
|
||||||
if in.Dependencies != nil {
|
if in.Dependencies != nil {
|
||||||
in, out := &in.Dependencies, &out.Dependencies
|
in, out := &in.Dependencies, &out.Dependencies
|
||||||
*out = make([]DependentObjectReference, len(*in))
|
*out = make([]DependentObjectReference, len(*in))
|
||||||
copy(*out, *in)
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if in.RawStatus != nil {
|
if in.RawStatus != nil {
|
||||||
in, out := &in.RawStatus, &out.RawStatus
|
in, out := &in.RawStatus, &out.RawStatus
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
|
@ -54,6 +53,13 @@ const (
|
||||||
bindingDependenciesAnnotationKey = "resourcebinding.karmada.io/dependencies"
|
bindingDependenciesAnnotationKey = "resourcebinding.karmada.io/dependencies"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LabelsKey is the object key which is a unique identifier under a cluster, across all resources.
|
||||||
|
type LabelsKey struct {
|
||||||
|
keys.ClusterWideKey
|
||||||
|
// Labels is the labels of the referencing object.
|
||||||
|
Labels map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
// DependenciesDistributor is to automatically propagate relevant resources.
|
// DependenciesDistributor is to automatically propagate relevant resources.
|
||||||
// Resource binding will be created when a resource(e.g. deployment) is matched by a propagation policy, we call it independent binding in DependenciesDistributor.
|
// Resource binding will be created when a resource(e.g. deployment) is matched by a propagation policy, we call it independent binding in DependenciesDistributor.
|
||||||
// And when DependenciesDistributor works, it will create or update reference resource bindings of relevant resources(e.g. secret), which we call them attached bindings.
|
// And when DependenciesDistributor works, it will create or update reference resource bindings of relevant resources(e.g. secret), which we call them attached bindings.
|
||||||
|
@ -111,7 +117,7 @@ func (d *DependenciesDistributor) OnUpdate(oldObj, newObj interface{}) {
|
||||||
klog.V(4).Infof("Ignore update event of object (%s, kind=%s, %s) as specification no change", unstructuredOldObj.GetAPIVersion(), unstructuredOldObj.GetKind(), names.NamespacedKey(unstructuredOldObj.GetNamespace(), unstructuredOldObj.GetName()))
|
klog.V(4).Infof("Ignore update event of object (%s, kind=%s, %s) as specification no change", unstructuredOldObj.GetAPIVersion(), unstructuredOldObj.GetKind(), names.NamespacedKey(unstructuredOldObj.GetNamespace(), unstructuredOldObj.GetName()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
d.OnAdd(oldObj)
|
||||||
d.OnAdd(newObj)
|
d.OnAdd(newObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +129,7 @@ func (d *DependenciesDistributor) OnDelete(obj interface{}) {
|
||||||
// Reconcile performs a full reconciliation for the object referred to by the key.
|
// Reconcile performs a full reconciliation for the object referred to by the key.
|
||||||
// The key will be re-queued if an error is non-nil.
|
// The key will be re-queued if an error is non-nil.
|
||||||
func (d *DependenciesDistributor) reconcile(key util.QueueKey) error {
|
func (d *DependenciesDistributor) reconcile(key util.QueueKey) error {
|
||||||
clusterWideKey, ok := key.(keys.ClusterWideKey)
|
clusterWideKey, ok := key.(*LabelsKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
klog.Error("Invalid key")
|
klog.Error("Invalid key")
|
||||||
return fmt.Errorf("invalid key")
|
return fmt.Errorf("invalid key")
|
||||||
|
@ -157,7 +163,7 @@ func (d *DependenciesDistributor) reconcile(key util.QueueKey) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// dependentObjectReferenceMatches tells if the given object is referred by current resource binding.
|
// dependentObjectReferenceMatches tells if the given object is referred by current resource binding.
|
||||||
func dependentObjectReferenceMatches(objectKey keys.ClusterWideKey, referenceBinding *workv1alpha2.ResourceBinding) bool {
|
func dependentObjectReferenceMatches(objectKey *LabelsKey, referenceBinding *workv1alpha2.ResourceBinding) bool {
|
||||||
dependencies, exist := referenceBinding.Annotations[bindingDependenciesAnnotationKey]
|
dependencies, exist := referenceBinding.Annotations[bindingDependenciesAnnotationKey]
|
||||||
if !exist {
|
if !exist {
|
||||||
return false
|
return false
|
||||||
|
@ -181,9 +187,17 @@ func dependentObjectReferenceMatches(objectKey keys.ClusterWideKey, referenceBin
|
||||||
for _, dependence := range dependenciesSlice {
|
for _, dependence := range dependenciesSlice {
|
||||||
if objectKey.GroupVersion().String() == dependence.APIVersion &&
|
if objectKey.GroupVersion().String() == dependence.APIVersion &&
|
||||||
objectKey.Kind == dependence.Kind &&
|
objectKey.Kind == dependence.Kind &&
|
||||||
objectKey.Namespace == dependence.Namespace &&
|
objectKey.Namespace == dependence.Namespace {
|
||||||
objectKey.Name == dependence.Name {
|
if len(dependence.Name) != 0 {
|
||||||
return true
|
return dependence.Name == objectKey.Name
|
||||||
|
}
|
||||||
|
var selector labels.Selector
|
||||||
|
if selector, err = metav1.LabelSelectorAsSelector(dependence.LabelSelector); err != nil {
|
||||||
|
klog.Errorf("Failed to converts the LabelSelector of binding(%s/%s) dependencies(%s): %v",
|
||||||
|
referenceBinding.Namespace, referenceBinding.Name, dependencies, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return selector.Matches(labels.Set(objectKey.Labels))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -239,6 +253,60 @@ func (d *DependenciesDistributor) handleResourceBindingDeletion(namespace, name
|
||||||
return d.removeScheduleResultFromAttachedBindings(namespace, name, attachedBindings)
|
return d.removeScheduleResultFromAttachedBindings(namespace, name, attachedBindings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DependenciesDistributor) removeOrphanAttachedBindings(binding *workv1alpha2.ResourceBinding, dependencies []configv1alpha1.DependentObjectReference) error {
|
||||||
|
// remove orphan attached bindings
|
||||||
|
orphanBindings, err := d.findOrphanAttachedBindings(binding, dependencies)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("Failed to find orphan attached bindings for resourceBinding(%s/%s). Error: %v.",
|
||||||
|
binding.GetNamespace(), binding.GetName(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = d.removeScheduleResultFromAttachedBindings(binding.Namespace, binding.Name, orphanBindings)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("Failed to remove orphan attached bindings by resourceBinding(%s/%s). Error: %v.",
|
||||||
|
binding.GetNamespace(), binding.GetName(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DependenciesDistributor) handleDependentResource(
|
||||||
|
binding *workv1alpha2.ResourceBinding,
|
||||||
|
resource workv1alpha2.ObjectReference,
|
||||||
|
dependent configv1alpha1.DependentObjectReference) error {
|
||||||
|
switch {
|
||||||
|
case len(dependent.Name) != 0:
|
||||||
|
rawObject, err := helper.FetchResourceTemplate(d.DynamicClient, d.InformerManager, d.RESTMapper, resource)
|
||||||
|
if err != nil {
|
||||||
|
// do nothing if resource template not exist.
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
attachedBinding := buildAttachedBinding(binding, rawObject)
|
||||||
|
return d.createOrUpdateAttachedBinding(attachedBinding)
|
||||||
|
case dependent.LabelSelector != nil:
|
||||||
|
var selector labels.Selector
|
||||||
|
var err error
|
||||||
|
if selector, err = metav1.LabelSelectorAsSelector(dependent.LabelSelector); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawObjects, err := helper.FetchResourceTemplateByLabelSelector(d.DynamicClient, d.InformerManager, d.RESTMapper, resource, selector)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rawObject := range rawObjects {
|
||||||
|
attachedBinding := buildAttachedBinding(binding, rawObject)
|
||||||
|
if err := d.createOrUpdateAttachedBinding(attachedBinding); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("the Name and LabelSelector in the DependentObjectReference of object (kind=%s %s/%s) cannot be empty at the same time", resource.Kind, resource.Namespace, resource.Name)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DependenciesDistributor) syncScheduleResultToAttachedBindings(binding *workv1alpha2.ResourceBinding, dependencies []configv1alpha1.DependentObjectReference) (err error) {
|
func (d *DependenciesDistributor) syncScheduleResultToAttachedBindings(binding *workv1alpha2.ResourceBinding, dependencies []configv1alpha1.DependentObjectReference) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -251,19 +319,7 @@ func (d *DependenciesDistributor) syncScheduleResultToAttachedBindings(binding *
|
||||||
if err := d.recordDependencies(binding, dependencies); err != nil {
|
if err := d.recordDependencies(binding, dependencies); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := d.removeOrphanAttachedBindings(binding, dependencies); err != nil {
|
||||||
// remove orphan attached bindings
|
|
||||||
orphanBindings, err := d.findOrphanAttachedBindings(binding, dependencies)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to find orphan attached bindings for resourceBinding(%s/%s). Error: %v.",
|
|
||||||
binding.GetNamespace(), binding.GetName(), err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.removeScheduleResultFromAttachedBindings(binding.Namespace, binding.Name, orphanBindings)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to remove orphan attached bindings by resourceBinding(%s/%s). Error: %v.",
|
|
||||||
binding.GetNamespace(), binding.GetName(), err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,20 +342,7 @@ func (d *DependenciesDistributor) syncScheduleResultToAttachedBindings(binding *
|
||||||
d.InformerManager.ForResource(gvr, d.eventHandler)
|
d.InformerManager.ForResource(gvr, d.eventHandler)
|
||||||
startInformerManager = true
|
startInformerManager = true
|
||||||
}
|
}
|
||||||
rawObject, err := helper.FetchResourceTemplate(d.DynamicClient, d.InformerManager, d.RESTMapper, resource)
|
errs = append(errs, d.handleDependentResource(binding, resource, dependent))
|
||||||
if err != nil {
|
|
||||||
// do nothing if resource template not exist.
|
|
||||||
if apierrors.IsNotFound(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
errs = append(errs, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
attachedBinding := buildAttachedBinding(binding, rawObject)
|
|
||||||
if err := d.createOrUpdateAttachedBinding(attachedBinding); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if startInformerManager {
|
if startInformerManager {
|
||||||
d.InformerManager.Start()
|
d.InformerManager.Start()
|
||||||
|
@ -351,22 +394,72 @@ func (d *DependenciesDistributor) findOrphanAttachedBindings(independentBinding
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dependenciesSets := sets.NewString()
|
dependenciesMaps := make(map[string][]int, 0)
|
||||||
for _, dependency := range dependencies {
|
for index, dependency := range dependencies {
|
||||||
key := generateDependencyKey(dependency.Kind, dependency.APIVersion, dependency.Namespace, dependency.Name)
|
key := generateDependencyKey(dependency.Kind, dependency.APIVersion, dependency.Namespace)
|
||||||
dependenciesSets.Insert(key)
|
dependenciesMaps[key] = append(dependenciesMaps[key], index)
|
||||||
}
|
}
|
||||||
|
|
||||||
var orphanAttachedBindings []*workv1alpha2.ResourceBinding
|
var orphanAttachedBindings []*workv1alpha2.ResourceBinding
|
||||||
for _, attachedBinding := range attachedBindings {
|
for _, attachedBinding := range attachedBindings {
|
||||||
key := generateDependencyKey(attachedBinding.Spec.Resource.Kind, attachedBinding.Spec.Resource.APIVersion, attachedBinding.Spec.Resource.Namespace, attachedBinding.Spec.Resource.Name)
|
key := generateDependencyKey(attachedBinding.Spec.Resource.Kind, attachedBinding.Spec.Resource.APIVersion, attachedBinding.Spec.Resource.Namespace)
|
||||||
if !dependenciesSets.Has(key) {
|
dependencyIndexes, exist := dependenciesMaps[key]
|
||||||
|
if !exist {
|
||||||
|
orphanAttachedBindings = append(orphanAttachedBindings, attachedBinding)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isOrphanAttachedBinding, err := d.isOrphanAttachedBindings(dependencies, dependencyIndexes, attachedBinding)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isOrphanAttachedBinding {
|
||||||
orphanAttachedBindings = append(orphanAttachedBindings, attachedBinding)
|
orphanAttachedBindings = append(orphanAttachedBindings, attachedBinding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return orphanAttachedBindings, nil
|
return orphanAttachedBindings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DependenciesDistributor) isOrphanAttachedBindings(
|
||||||
|
dependencies []configv1alpha1.DependentObjectReference,
|
||||||
|
dependencyIndexes []int,
|
||||||
|
attachedBinding *workv1alpha2.ResourceBinding) (bool, error) {
|
||||||
|
var resource = attachedBinding.Spec.Resource
|
||||||
|
for _, idx := range dependencyIndexes {
|
||||||
|
dependency := dependencies[idx]
|
||||||
|
switch {
|
||||||
|
case len(dependency.Name) != 0:
|
||||||
|
if dependency.Name == resource.Name {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
case dependency.LabelSelector != nil:
|
||||||
|
var selector labels.Selector
|
||||||
|
var err error
|
||||||
|
if selector, err = metav1.LabelSelectorAsSelector(dependency.LabelSelector); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
rawObject, err := helper.FetchResourceTemplate(d.DynamicClient, d.InformerManager, d.RESTMapper, workv1alpha2.ObjectReference{
|
||||||
|
APIVersion: resource.APIVersion,
|
||||||
|
Kind: resource.Kind,
|
||||||
|
Namespace: resource.Namespace,
|
||||||
|
Name: resource.Name,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// do nothing if resource template not exist.
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if selector.Matches(labels.Set(rawObject.GetLabels())) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// can not reach here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DependenciesDistributor) listAttachedBindings(bindingNamespace, bindingName string) (res []*workv1alpha2.ResourceBinding, err error) {
|
func (d *DependenciesDistributor) listAttachedBindings(bindingNamespace, bindingName string) (res []*workv1alpha2.ResourceBinding, err error) {
|
||||||
labelSet := generateBindingDependedLabels(bindingNamespace, bindingName)
|
labelSet := generateBindingDependedLabels(bindingNamespace, bindingName)
|
||||||
selector := labels.SelectorFromSet(labelSet)
|
selector := labels.SelectorFromSet(labelSet)
|
||||||
|
@ -435,8 +528,21 @@ func (d *DependenciesDistributor) Start(ctx context.Context) error {
|
||||||
klog.Infof("Starting dependencies distributor.")
|
klog.Infof("Starting dependencies distributor.")
|
||||||
d.stopCh = ctx.Done()
|
d.stopCh = ctx.Done()
|
||||||
resourceWorkerOptions := util.Options{
|
resourceWorkerOptions := util.Options{
|
||||||
Name: "dependencies resource detector",
|
Name: "dependencies resource detector",
|
||||||
KeyFunc: func(obj interface{}) (util.QueueKey, error) { return keys.ClusterWideKeyFunc(obj) },
|
KeyFunc: func(obj interface{}) (util.QueueKey, error) {
|
||||||
|
key, err := keys.ClusterWideKeyFunc(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
metaInfo, err := meta.Accessor(obj)
|
||||||
|
if err != nil { // should not happen
|
||||||
|
return nil, fmt.Errorf("object has no meta: %v", err)
|
||||||
|
}
|
||||||
|
return &LabelsKey{
|
||||||
|
ClusterWideKey: key,
|
||||||
|
Labels: metaInfo.GetLabels(),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
ReconcileFunc: d.reconcile,
|
ReconcileFunc: d.reconcile,
|
||||||
}
|
}
|
||||||
d.eventHandler = fedinformer.NewHandlerOnEvents(d.OnAdd, d.OnUpdate, d.OnDelete)
|
d.eventHandler = fedinformer.NewHandlerOnEvents(d.OnAdd, d.OnUpdate, d.OnDelete)
|
||||||
|
@ -506,12 +612,12 @@ func generateBindingDependedLabelKey(bindingNamespace, bindingName string) strin
|
||||||
return fmt.Sprintf(bindingDependedByLabelKeyPrefix + bindHashKey)
|
return fmt.Sprintf(bindingDependedByLabelKeyPrefix + bindHashKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateDependencyKey(kind, apiVersion, name, namespace string) string {
|
func generateDependencyKey(kind, apiVersion, namespace string) string {
|
||||||
if len(namespace) == 0 {
|
if len(namespace) == 0 {
|
||||||
return kind + "-" + apiVersion + "-" + name
|
return kind + "-" + apiVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
return kind + "-" + apiVersion + "-" + namespace + "-" + name
|
return kind + "-" + apiVersion + "-" + namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAttachedBinding(binding *workv1alpha2.ResourceBinding, object *unstructured.Unstructured) *workv1alpha2.ResourceBinding {
|
func buildAttachedBinding(binding *workv1alpha2.ResourceBinding, object *unstructured.Unstructured) *workv1alpha2.ResourceBinding {
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
package dependenciesdistributor
|
package dependenciesdistributor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
|
||||||
|
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||||
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/util/fedinformer/genericmanager"
|
||||||
"github.com/karmada-io/karmada/pkg/util/fedinformer/keys"
|
"github.com/karmada-io/karmada/pkg/util/fedinformer/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_dependentObjectReferenceMatches(t *testing.T) {
|
func Test_dependentObjectReferenceMatches(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
objectKey keys.ClusterWideKey
|
objectKey *LabelsKey
|
||||||
referenceBinding *workv1alpha2.ResourceBinding
|
referenceBinding *workv1alpha2.ResourceBinding
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -22,12 +32,15 @@ func Test_dependentObjectReferenceMatches(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test custom resource",
|
name: "test custom resource",
|
||||||
args: args{
|
args: args{
|
||||||
objectKey: keys.ClusterWideKey{
|
objectKey: &LabelsKey{
|
||||||
Group: "example-stgzr.karmada.io",
|
ClusterWideKey: keys.ClusterWideKey{
|
||||||
Version: "v1alpha1",
|
Group: "example-stgzr.karmada.io",
|
||||||
Kind: "Foot5zmh",
|
Version: "v1alpha1",
|
||||||
Namespace: "karmadatest-vpvll",
|
Kind: "Foot5zmh",
|
||||||
Name: "cr-fxzq6",
|
Namespace: "karmadatest-vpvll",
|
||||||
|
Name: "cr-fxzq6",
|
||||||
|
},
|
||||||
|
Labels: nil,
|
||||||
},
|
},
|
||||||
referenceBinding: &workv1alpha2.ResourceBinding{
|
referenceBinding: &workv1alpha2.ResourceBinding{
|
||||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
|
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
|
||||||
|
@ -40,12 +53,15 @@ func Test_dependentObjectReferenceMatches(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test configmap",
|
name: "test configmap",
|
||||||
args: args{
|
args: args{
|
||||||
objectKey: keys.ClusterWideKey{
|
objectKey: &LabelsKey{
|
||||||
Group: "",
|
ClusterWideKey: keys.ClusterWideKey{
|
||||||
Version: "v1",
|
Group: "",
|
||||||
Kind: "ConfigMap",
|
Version: "v1",
|
||||||
Namespace: "karmadatest-h46wh",
|
Kind: "ConfigMap",
|
||||||
Name: "configmap-8w426",
|
Namespace: "karmadatest-h46wh",
|
||||||
|
Name: "configmap-8w426",
|
||||||
|
},
|
||||||
|
Labels: nil,
|
||||||
},
|
},
|
||||||
referenceBinding: &workv1alpha2.ResourceBinding{
|
referenceBinding: &workv1alpha2.ResourceBinding{
|
||||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
|
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
|
||||||
|
@ -55,6 +71,28 @@ func Test_dependentObjectReferenceMatches(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "test labels",
|
||||||
|
args: args{
|
||||||
|
objectKey: &LabelsKey{
|
||||||
|
ClusterWideKey: keys.ClusterWideKey{
|
||||||
|
Group: "",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
Namespace: "test",
|
||||||
|
},
|
||||||
|
Labels: map[string]string{
|
||||||
|
"app": "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
referenceBinding: &workv1alpha2.ResourceBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
|
||||||
|
bindingDependenciesAnnotationKey: "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"namespace\":\"test\",\"labelSelector\":{\"matchExpressions\":[{\"key\":\"app\",\"operator\":\"In\",\"values\":[\"test\"]}]}}]",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -65,3 +103,141 @@ func Test_dependentObjectReferenceMatches(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDependenciesDistributor_findOrphanAttachedBindingsByDependencies(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
DynamicClient dynamic.Interface
|
||||||
|
InformerManager genericmanager.SingleClusterInformerManager
|
||||||
|
RESTMapper meta.RESTMapper
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
dependencies []configv1alpha1.DependentObjectReference
|
||||||
|
dependencyIndexes []int
|
||||||
|
attachedBinding *workv1alpha2.ResourceBinding
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "can't match labels",
|
||||||
|
fields: fields{
|
||||||
|
DynamicClient: dynamicfake.NewSimpleDynamicClient(scheme.Scheme),
|
||||||
|
InformerManager: func() genericmanager.SingleClusterInformerManager {
|
||||||
|
c := dynamicfake.NewSimpleDynamicClient(scheme.Scheme,
|
||||||
|
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "default", Labels: map[string]string{"bar": "bar"}}})
|
||||||
|
m := genericmanager.NewSingleClusterInformerManager(c, 0, context.TODO().Done())
|
||||||
|
m.Lister(corev1.SchemeGroupVersion.WithResource("pods"))
|
||||||
|
m.Start()
|
||||||
|
m.WaitForCacheSync()
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
RESTMapper: func() meta.RESTMapper {
|
||||||
|
m := meta.NewDefaultRESTMapper([]schema.GroupVersion{corev1.SchemeGroupVersion})
|
||||||
|
m.Add(corev1.SchemeGroupVersion.WithKind("Pod"), meta.RESTScopeNamespace)
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
dependencies: []configv1alpha1.DependentObjectReference{
|
||||||
|
{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Pod",
|
||||||
|
Namespace: "default",
|
||||||
|
LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
|
||||||
|
"bar": "foo",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencyIndexes: []int{1},
|
||||||
|
attachedBinding: &workv1alpha2.ResourceBinding{
|
||||||
|
Spec: workv1alpha2.ResourceBindingSpec{
|
||||||
|
Resource: workv1alpha2.ObjectReference{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Pod",
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "pod",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "can't match name",
|
||||||
|
fields: fields{
|
||||||
|
DynamicClient: dynamicfake.NewSimpleDynamicClient(scheme.Scheme),
|
||||||
|
InformerManager: func() genericmanager.SingleClusterInformerManager {
|
||||||
|
c := dynamicfake.NewSimpleDynamicClient(scheme.Scheme,
|
||||||
|
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "default", Labels: map[string]string{"bar": "foo"}}})
|
||||||
|
m := genericmanager.NewSingleClusterInformerManager(c, 0, context.TODO().Done())
|
||||||
|
m.Lister(corev1.SchemeGroupVersion.WithResource("pods"))
|
||||||
|
m.Start()
|
||||||
|
m.WaitForCacheSync()
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
RESTMapper: func() meta.RESTMapper {
|
||||||
|
m := meta.NewDefaultRESTMapper([]schema.GroupVersion{corev1.SchemeGroupVersion})
|
||||||
|
m.Add(corev1.SchemeGroupVersion.WithKind("Pod"), meta.RESTScopeNamespace)
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
dependencies: []configv1alpha1.DependentObjectReference{
|
||||||
|
{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Pod",
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "pod",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencyIndexes: []int{1},
|
||||||
|
attachedBinding: &workv1alpha2.ResourceBinding{
|
||||||
|
Spec: workv1alpha2.ResourceBindingSpec{
|
||||||
|
Resource: workv1alpha2.ObjectReference{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Pod",
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
d := &DependenciesDistributor{
|
||||||
|
DynamicClient: tt.fields.DynamicClient,
|
||||||
|
InformerManager: tt.fields.InformerManager,
|
||||||
|
RESTMapper: tt.fields.RESTMapper,
|
||||||
|
}
|
||||||
|
got, err := d.isOrphanAttachedBindings(tt.args.dependencies, tt.args.dependencyIndexes, tt.args.attachedBinding)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("findOrphanAttachedBindingsByDependencies() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("findOrphanAttachedBindingsByDependencies() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1811,16 +1811,23 @@ func schema_pkg_apis_config_v1alpha1_DependentObjectReference(ref common.Referen
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Description: "Name represents the name of the referent.",
|
Description: "Name represents the name of the referent. Name and LabelSelector cannot be empty at the same time.",
|
||||||
Default: "",
|
|
||||||
Type: []string{"string"},
|
Type: []string{"string"},
|
||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"labelSelector": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "LabelSelector represents a label query over a set of resources. If name is not empty, labelSelector will be ignored. Name and LabelSelector cannot be empty at the same time.",
|
||||||
|
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"apiVersion", "kind", "name"},
|
Required: []string{"apiVersion", "kind"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Dependencies: []string{
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -251,6 +251,56 @@ func FetchResourceTemplate(
|
||||||
return unstructuredObj, nil
|
return unstructuredObj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FetchResourceTemplateByLabelSelector fetches the resource template by label selector to be propagated.
|
||||||
|
// Any updates to this resource template are not recommended as it may come from the informer cache.
|
||||||
|
// We should abide by the principle of making a deep copy first and then modifying it.
|
||||||
|
// See issue: https://github.com/karmada-io/karmada/issues/3878.
|
||||||
|
func FetchResourceTemplateByLabelSelector(
|
||||||
|
dynamicClient dynamic.Interface,
|
||||||
|
informerManager genericmanager.SingleClusterInformerManager,
|
||||||
|
restMapper meta.RESTMapper,
|
||||||
|
resource workv1alpha2.ObjectReference,
|
||||||
|
selector labels.Selector,
|
||||||
|
) ([]*unstructured.Unstructured, error) {
|
||||||
|
gvr, err := restmapper.GetGroupVersionResource(restMapper, schema.FromAPIVersionAndKind(resource.APIVersion, resource.Kind))
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("Failed to get GVR from GVK(%s/%s), Error: %v", resource.APIVersion, resource.Kind, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var objectList []runtime.Object
|
||||||
|
if len(resource.Namespace) == 0 {
|
||||||
|
// cluster-scoped resource
|
||||||
|
objectList, err = informerManager.Lister(gvr).List(selector)
|
||||||
|
} else {
|
||||||
|
objectList, err = informerManager.Lister(gvr).ByNamespace(resource.Namespace).List(selector)
|
||||||
|
}
|
||||||
|
var objects []*unstructured.Unstructured
|
||||||
|
if err != nil || len(objectList) == 0 {
|
||||||
|
// fall back to call api server in case the cache has not been synchronized yet
|
||||||
|
klog.Warningf("Failed to get resource template (%s/%s/%s) from cache, Error: %v. Fall back to call api server.",
|
||||||
|
resource.Kind, resource.Namespace, resource.Name, err)
|
||||||
|
unstructuredList, err := dynamicClient.Resource(gvr).Namespace(resource.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()})
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("Failed to get resource template (%s/%s/%s) from api server, Error: %v",
|
||||||
|
resource.Kind, resource.Namespace, resource.Name, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range unstructuredList.Items {
|
||||||
|
objects = append(objects, &unstructuredList.Items[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range objectList {
|
||||||
|
unstructuredObj, err := ToUnstructured(objectList[i])
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("Failed to transform object(%s/%s), Error: %v", resource.Namespace, resource.Name, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objects = append(objects, unstructuredObj)
|
||||||
|
}
|
||||||
|
return objects, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetClusterResourceBindings returns a ClusterResourceBindingList by labels.
|
// GetClusterResourceBindings returns a ClusterResourceBindingList by labels.
|
||||||
func GetClusterResourceBindings(c client.Client, ls labels.Set) (*workv1alpha2.ClusterResourceBindingList, error) {
|
func GetClusterResourceBindings(c client.Client, ls labels.Set) (*workv1alpha2.ClusterResourceBindingList, error) {
|
||||||
bindings := &workv1alpha2.ClusterResourceBindingList{}
|
bindings := &workv1alpha2.ClusterResourceBindingList{}
|
||||||
|
|
|
@ -801,6 +801,154 @@ func TestFetchWorkload(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFetchWorkloadByLabelSelector(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
dynamicClient dynamic.Interface
|
||||||
|
informerManager func(stopCh <-chan struct{}) genericmanager.SingleClusterInformerManager
|
||||||
|
restMapper meta.RESTMapper
|
||||||
|
resource workv1alpha2.ObjectReference
|
||||||
|
selector *metav1.LabelSelector
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "kind is not registered",
|
||||||
|
args: args{
|
||||||
|
dynamicClient: dynamicfake.NewSimpleDynamicClient(scheme.Scheme),
|
||||||
|
informerManager: func(stopCh <-chan struct{}) genericmanager.SingleClusterInformerManager {
|
||||||
|
return genericmanager.NewSingleClusterInformerManager(dynamicfake.NewSimpleDynamicClient(scheme.Scheme), 0, stopCh)
|
||||||
|
},
|
||||||
|
restMapper: meta.NewDefaultRESTMapper(nil),
|
||||||
|
resource: workv1alpha2.ObjectReference{APIVersion: "v1", Kind: "Pod"},
|
||||||
|
},
|
||||||
|
want: 0,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace scope: get from client",
|
||||||
|
args: args{
|
||||||
|
dynamicClient: dynamicfake.NewSimpleDynamicClient(scheme.Scheme,
|
||||||
|
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "default", Labels: map[string]string{"foo": "foo"}}}),
|
||||||
|
informerManager: func(stopCh <-chan struct{}) genericmanager.SingleClusterInformerManager {
|
||||||
|
return genericmanager.NewSingleClusterInformerManager(dynamicfake.NewSimpleDynamicClient(scheme.Scheme), 0, stopCh)
|
||||||
|
},
|
||||||
|
restMapper: func() meta.RESTMapper {
|
||||||
|
m := meta.NewDefaultRESTMapper([]schema.GroupVersion{corev1.SchemeGroupVersion})
|
||||||
|
m.Add(corev1.SchemeGroupVersion.WithKind("Pod"), meta.RESTScopeNamespace)
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
resource: workv1alpha2.ObjectReference{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Pod",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "foo"}},
|
||||||
|
},
|
||||||
|
want: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace scope: get from cache",
|
||||||
|
args: args{
|
||||||
|
dynamicClient: dynamicfake.NewSimpleDynamicClient(scheme.Scheme),
|
||||||
|
informerManager: func(stopCh <-chan struct{}) genericmanager.SingleClusterInformerManager {
|
||||||
|
c := dynamicfake.NewSimpleDynamicClient(scheme.Scheme,
|
||||||
|
&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "default", Labels: map[string]string{"bar": "foo"}}})
|
||||||
|
m := genericmanager.NewSingleClusterInformerManager(c, 0, stopCh)
|
||||||
|
m.Lister(corev1.SchemeGroupVersion.WithResource("pods"))
|
||||||
|
m.Start()
|
||||||
|
m.WaitForCacheSync()
|
||||||
|
return m
|
||||||
|
},
|
||||||
|
restMapper: func() meta.RESTMapper {
|
||||||
|
m := meta.NewDefaultRESTMapper([]schema.GroupVersion{corev1.SchemeGroupVersion})
|
||||||
|
m.Add(corev1.SchemeGroupVersion.WithKind("Pod"), meta.RESTScopeNamespace)
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
resource: workv1alpha2.ObjectReference{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Pod",
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "pod",
|
||||||
|
},
|
||||||
|
selector: &metav1.LabelSelector{MatchLabels: map[string]string{"bar": "foo"}},
|
||||||
|
},
|
||||||
|
want: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cluster scope: get from client",
|
||||||
|
args: args{
|
||||||
|
dynamicClient: dynamicfake.NewSimpleDynamicClient(scheme.Scheme,
|
||||||
|
&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node", Labels: map[string]string{"bar": "bar"}}}),
|
||||||
|
informerManager: func(stopCh <-chan struct{}) genericmanager.SingleClusterInformerManager {
|
||||||
|
return genericmanager.NewSingleClusterInformerManager(dynamicfake.NewSimpleDynamicClient(scheme.Scheme), 0, stopCh)
|
||||||
|
},
|
||||||
|
restMapper: func() meta.RESTMapper {
|
||||||
|
m := meta.NewDefaultRESTMapper([]schema.GroupVersion{corev1.SchemeGroupVersion})
|
||||||
|
m.Add(corev1.SchemeGroupVersion.WithKind("Node"), meta.RESTScopeRoot)
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
resource: workv1alpha2.ObjectReference{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Node",
|
||||||
|
Name: "node",
|
||||||
|
},
|
||||||
|
selector: &metav1.LabelSelector{MatchLabels: map[string]string{"bar": "bar"}},
|
||||||
|
},
|
||||||
|
want: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cluster scope: get from cache",
|
||||||
|
args: args{
|
||||||
|
dynamicClient: dynamicfake.NewSimpleDynamicClient(scheme.Scheme),
|
||||||
|
informerManager: func(stopCh <-chan struct{}) genericmanager.SingleClusterInformerManager {
|
||||||
|
c := dynamicfake.NewSimpleDynamicClient(scheme.Scheme,
|
||||||
|
&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node", Labels: map[string]string{"bar": "foo"}}})
|
||||||
|
m := genericmanager.NewSingleClusterInformerManager(c, 0, stopCh)
|
||||||
|
m.Lister(corev1.SchemeGroupVersion.WithResource("nodes"))
|
||||||
|
m.Start()
|
||||||
|
m.WaitForCacheSync()
|
||||||
|
return m
|
||||||
|
},
|
||||||
|
restMapper: func() meta.RESTMapper {
|
||||||
|
m := meta.NewDefaultRESTMapper([]schema.GroupVersion{corev1.SchemeGroupVersion})
|
||||||
|
m.Add(corev1.SchemeGroupVersion.WithKind("Node"), meta.RESTScopeRoot)
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
resource: workv1alpha2.ObjectReference{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Node",
|
||||||
|
Name: "node",
|
||||||
|
},
|
||||||
|
selector: &metav1.LabelSelector{MatchLabels: map[string]string{"bar": "foo"}},
|
||||||
|
},
|
||||||
|
want: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
mgr := tt.args.informerManager(stopCh)
|
||||||
|
selector, _ := metav1.LabelSelectorAsSelector(tt.args.selector)
|
||||||
|
got, err := FetchResourceTemplateByLabelSelector(tt.args.dynamicClient, mgr, tt.args.restMapper, tt.args.resource, selector)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("FetchResourceTemplate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(got) != tt.want {
|
||||||
|
t.Errorf("FetchResourceTemplate() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeleteWorkByRBNamespaceAndName(t *testing.T) {
|
func TestDeleteWorkByRBNamespaceAndName(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
c client.Client
|
c client.Client
|
||||||
|
|
Loading…
Reference in New Issue