update propagation policy api, make namespace and name singular in resourceSelector (#107)

* update propagation policy api, make namespace and name singular in resourceSelector

Signed-off-by: Kevin Wang <kevinwzf0126@gmail.com>
This commit is contained in:
Kevin Wang 2021-01-07 10:25:28 +08:00 committed by GitHub
parent abae19c212
commit b3b08d6d70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 168 deletions

View File

@ -71,18 +71,12 @@ spec:
description: APIVersion represents the API version of the target description: APIVersion represents the API version of the target
resources. resources.
type: string type: string
excludeNamespaces:
description: ExcludeNamespaces is a list of namespaces that
the ResourceSelector will ignore. Default is empty, which
means don't ignore any namespace.
items:
type: string
type: array
kind: kind:
description: Kind represents the Kind of the target resources. description: Kind represents the Kind of the target resources.
type: string type: string
labelSelector: labelSelector:
description: A label query over a set of resources. description: A label query over a set of resources. If name
is not empty, labelSelector will be ignored.
properties: properties:
matchExpressions: matchExpressions:
description: matchExpressions is a list of label selector description: matchExpressions is a list of label selector
@ -125,21 +119,14 @@ spec:
only "value". The requirements are ANDed. only "value". The requirements are ANDed.
type: object type: object
type: object type: object
names: name:
description: Names restricts a list of referent names that the description: Name of the target resource. Default is empty,
ResourceSelector will only select. Default is empty, which which means selecting all resources.
means selecting all resources.
items:
type: string type: string
type: array namespace:
namespaces: description: Namespace of the target resource. Default is empty,
description: Namespaces restricts a list of namespaces that which means inherit from the parent object scope.
the ResourceSelector will only select. If set, only resources
in the listed namespaces will be selected. Default is empty,
which means selecting all namespaces.
items:
type: string type: string
type: array
required: required:
- apiVersion - apiVersion
- kind - kind

View File

@ -219,7 +219,7 @@ spec:
type: object type: object
type: array type: array
type: object type: object
resourceSelector: resourceSelectors:
description: ResourceSelectors used to select resources. nil represents description: ResourceSelectors used to select resources. nil represents
all resources. all resources.
items: items:
@ -229,18 +229,12 @@ spec:
description: APIVersion represents the API version of the target description: APIVersion represents the API version of the target
resources. resources.
type: string type: string
excludeNamespaces:
description: ExcludeNamespaces is a list of namespaces that
the ResourceSelector will ignore. Default is empty, which
means don't ignore any namespace.
items:
type: string
type: array
kind: kind:
description: Kind represents the Kind of the target resources. description: Kind represents the Kind of the target resources.
type: string type: string
labelSelector: labelSelector:
description: A label query over a set of resources. description: A label query over a set of resources. If name
is not empty, labelSelector will be ignored.
properties: properties:
matchExpressions: matchExpressions:
description: matchExpressions is a list of label selector description: matchExpressions is a list of label selector
@ -283,21 +277,14 @@ spec:
only "value". The requirements are ANDed. only "value". The requirements are ANDed.
type: object type: object
type: object type: object
names: name:
description: Names restricts a list of referent names that the description: Name of the target resource. Default is empty,
ResourceSelector will only select. Default is empty, which which means selecting all resources.
means selecting all resources.
items:
type: string type: string
type: array namespace:
namespaces: description: Namespace of the target resource. Default is empty,
description: Namespaces restricts a list of namespaces that which means inherit from the parent object scope.
the ResourceSelector will only select. If set, only resources
in the listed namespaces will be selected. Default is empty,
which means selecting all namespaces.
items:
type: string type: string
type: array
required: required:
- apiVersion - apiVersion
- kind - kind

View File

@ -4,16 +4,10 @@ metadata:
name: example-policy name: example-policy
namespace: default namespace: default
spec: spec:
resourceSelector: resourceSelectors:
- apiVersion: apps/v1 - apiVersion: apps/v1
kind: Deployment kind: Deployment
names: name: nginx
- nginx
namespaces:
- default
- exclude
excludeNamespaces:
- exclude
labelSelector: labelSelector:
matchLabels: matchLabels:
a: b a: b

View File

@ -2,25 +2,19 @@ apiVersion: propagationstrategy.karmada.io/v1alpha1
kind: PropagationPolicy kind: PropagationPolicy
metadata: metadata:
name: example-policy name: example-policy
namespace: default
spec: spec:
resourceSelector: resourceSelector:
- apiVersion: apps/v1 - apiVersion: apps/v1
kind: Deployment kind: Deployment
names: name: deployment-1
- nginx labelSelector: # standard labelSelector
namespaces: propagateDependensies: false
- default
excludeNamespaces:
- exclude
association: false
placement: placement:
clusterAffinity: clusterAffinity:
clusterNames: clusterNames:
- cluster1 - cluster1
- cluster3 - cluster3
exclude: clusterTolerations: # like pod tolerations
- cluster2
spreadConstraints: spreadConstraints:
- spreadByLabel: failuredomain.kubernetes.io/zone - spreadByLabel: failuredomain.kubernetes.io/zone
maximum: 3 maximum: 3

View File

@ -22,7 +22,7 @@ type PropagationPolicy struct {
type PropagationSpec struct { type PropagationSpec struct {
// ResourceSelectors used to select resources. // ResourceSelectors used to select resources.
// nil represents all resources. // nil represents all resources.
ResourceSelectors []ResourceSelector `json:"resourceSelector,omitempty"` ResourceSelectors []ResourceSelector `json:"resourceSelectors,omitempty"`
// Association tells if relevant resources should be selected automatically. // Association tells if relevant resources should be selected automatically.
// e.g. a ConfigMap referred by a Deployment. // e.g. a ConfigMap referred by a Deployment.
@ -47,28 +47,20 @@ type ResourceSelector struct {
// Kind represents the Kind of the target resources. // Kind represents the Kind of the target resources.
Kind string `json:"kind"` Kind string `json:"kind"`
// Names restricts a list of referent names that the ResourceSelector will only select. // Namespace of the target resource.
// Default is empty, which means inherit from the parent object scope.
// +optional
Namespace string `json:"namespace,omitempty"`
// Name of the target resource.
// Default is empty, which means selecting all resources. // Default is empty, which means selecting all resources.
// +optional // +optional
Names []string `json:"names,omitempty"` Name string `json:"name,omitempty"`
// Namespaces restricts a list of namespaces that the ResourceSelector will only select.
// If set, only resources in the listed namespaces will be selected.
// Default is empty, which means selecting all namespaces.
// +optional
Namespaces []string `json:"namespaces,omitempty"`
// ExcludeNamespaces is a list of namespaces that the ResourceSelector will ignore.
// Default is empty, which means don't ignore any namespace.
// +optional
ExcludeNamespaces []string `json:"excludeNamespaces,omitempty"`
// A label query over a set of resources. // A label query over a set of resources.
// If name is not empty, labelSelector will be ignored.
// +optional // +optional
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"` LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
// FieldSelector is a field filter.
//FieldSelector *FieldSelector `json:"fieldSelector,omitempty"`
} }
// FieldSelector is a field filter. // FieldSelector is a field filter.

View File

@ -635,21 +635,6 @@ func (in *ResourceIdentifier) DeepCopy() *ResourceIdentifier {
// 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 *ResourceSelector) DeepCopyInto(out *ResourceSelector) { func (in *ResourceSelector) DeepCopyInto(out *ResourceSelector) {
*out = *in *out = *in
if in.Names != nil {
in, out := &in.Names, &out.Names
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ExcludeNamespaces != nil {
in, out := &in.ExcludeNamespaces, &out.ExcludeNamespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.LabelSelector != nil { if in.LabelSelector != nil {
in, out := &in.LabelSelector, &out.LabelSelector in, out := &in.LabelSelector, &out.LabelSelector
*out = new(v1.LabelSelector) *out = new(v1.LabelSelector)

View File

@ -71,7 +71,7 @@ func (c *PropagationPolicyController) Reconcile(req controllerruntime.Request) (
// syncPolicy will fetch matched resource by policy, then transform them to propagationBindings. // syncPolicy will fetch matched resource by policy, then transform them to propagationBindings.
func (c *PropagationPolicyController) syncPolicy(policy *v1alpha1.PropagationPolicy) (controllerruntime.Result, error) { func (c *PropagationPolicyController) syncPolicy(policy *v1alpha1.PropagationPolicy) (controllerruntime.Result, error) {
workloads, err := c.fetchWorkloads(policy.Spec.ResourceSelectors) workloads, err := c.fetchWorkloads(policy)
if err != nil { if err != nil {
return controllerruntime.Result{Requeue: true}, err return controllerruntime.Result{Requeue: true}, err
} }
@ -93,29 +93,19 @@ func (c *PropagationPolicyController) syncPolicy(policy *v1alpha1.PropagationPol
} }
// fetchWorkloads fetches all matched resources via resource selectors. // fetchWorkloads fetches all matched resources via resource selectors.
// TODO(RainbowMango): the implementation is old and too complicated, need refactor later. func (c *PropagationPolicyController) fetchWorkloads(policy *v1alpha1.PropagationPolicy) ([]*unstructured.Unstructured, error) {
func (c *PropagationPolicyController) fetchWorkloads(resourceSelectors []v1alpha1.ResourceSelector) ([]*unstructured.Unstructured, error) {
var workloads []*unstructured.Unstructured var workloads []*unstructured.Unstructured
// todo: if resources repetitive, deduplication.
// todo: if namespaces, names, labelSelector is nil, need to do something for _, resourceSelector := range policy.Spec.ResourceSelectors {
for _, resourceSelector := range resourceSelectors { if resourceSelector.Namespace == "" {
names := util.GetUniqueElements(resourceSelector.Names) resourceSelector.Namespace = policy.Namespace
namespaces := util.GetDifferenceSet(resourceSelector.Namespaces, resourceSelector.ExcludeNamespaces) }
for _, namespace := range namespaces { tmpWorkloads, err := c.fetchWorkloadsByResourceSelector(resourceSelector)
if resourceSelector.LabelSelector == nil {
err := c.fetchWorkloadsWithOutLabelSelector(resourceSelector, namespace, names, &workloads)
if err != nil { if err != nil {
klog.Errorf("Failed to fetch workloads by names in namespace %s. Error: %v.", namespace, err) klog.Errorf("Failed to fetch workloads with labelSelector in namespace %s. Error: %v.", policy.Namespace, err)
return nil, err return nil, err
} }
} else { workloads = append(workloads, tmpWorkloads...)
err := c.fetchWorkloadsWithLabelSelector(resourceSelector, namespace, names, &workloads)
if err != nil {
klog.Errorf("Failed to fetch workloads with labelSelector in namespace %s. Error: %v.", namespace, err)
return nil, err
}
}
}
} }
return workloads, nil return workloads, nil
} }
@ -237,65 +227,64 @@ func (c *PropagationPolicyController) ignoreIrrelevantWorkload(policy *v1alpha1.
return result return result
} }
// fetchWorkloadsWithLabelSelector query workloads by labelSelector and names // fetchWorkloadsByResourceSelector query workloads by labelSelector and names
func (c *PropagationPolicyController) fetchWorkloadsWithLabelSelector(resourceSelector v1alpha1.ResourceSelector, func (c *PropagationPolicyController) fetchWorkloadsByResourceSelector(resourceSelector v1alpha1.ResourceSelector) ([]*unstructured.Unstructured, error) {
namespace string, names []string, workloads *[]*unstructured.Unstructured) error {
dynamicResource, err := restmapper.GetGroupVersionResource(c.RESTMapper, dynamicResource, err := restmapper.GetGroupVersionResource(c.RESTMapper,
schema.FromAPIVersionAndKind(resourceSelector.APIVersion, resourceSelector.Kind)) schema.FromAPIVersionAndKind(resourceSelector.APIVersion, resourceSelector.Kind))
if err != nil { if err != nil {
klog.Errorf("Failed to get GVR from GVK %s %s. Error: %v", resourceSelector.APIVersion, resourceSelector.Kind, err) klog.Errorf("Failed to get GVR from GVK %s %s. Error: %v", resourceSelector.APIVersion, resourceSelector.Kind, err)
return err return nil, err
} }
unstructuredWorkLoadList, err := c.DynamicClient.Resource(dynamicResource).Namespace(namespace).List(context.TODO(),
metav1.ListOptions{LabelSelector: labels.Set(resourceSelector.LabelSelector.MatchLabels).String()}) if resourceSelector.Name != "" {
workload, err := c.fetchWorkload(resourceSelector)
if err != nil || workload == nil {
return nil, err
}
return []*unstructured.Unstructured{workload}, nil
}
unstructuredWorkLoadList, err := c.DynamicClient.Resource(dynamicResource).Namespace(resourceSelector.Namespace).List(context.TODO(),
metav1.ListOptions{LabelSelector: resourceSelector.LabelSelector.String()})
if err != nil { if err != nil {
return err return nil, err
} }
if resourceSelector.Names == nil {
var workloads []*unstructured.Unstructured
for _, unstructuredWorkLoad := range unstructuredWorkLoadList.Items { for _, unstructuredWorkLoad := range unstructuredWorkLoadList.Items {
if unstructuredWorkLoad.GetDeletionTimestamp() == nil { if unstructuredWorkLoad.GetDeletionTimestamp() == nil {
*workloads = append(*workloads, &unstructuredWorkLoad) workloads = append(workloads, &unstructuredWorkLoad)
} }
} }
} else {
for _, unstructuredWorkLoad := range unstructuredWorkLoadList.Items { return workloads, nil
for _, name := range names {
if unstructuredWorkLoad.GetName() == name && unstructuredWorkLoad.GetDeletionTimestamp() == nil {
*workloads = append(*workloads, &unstructuredWorkLoad)
break
}
}
}
}
return nil
} }
// fetchWorkloadsWithOutLabelSelector query workloads by names func (c *PropagationPolicyController) fetchWorkload(resourceSelector v1alpha1.ResourceSelector) (*unstructured.Unstructured, error) {
func (c *PropagationPolicyController) fetchWorkloadsWithOutLabelSelector(resourceSelector v1alpha1.ResourceSelector, namespace string, names []string, workloads *[]*unstructured.Unstructured) error {
for _, name := range names {
dynamicResource, err := restmapper.GetGroupVersionResource(c.RESTMapper, dynamicResource, err := restmapper.GetGroupVersionResource(c.RESTMapper,
schema.FromAPIVersionAndKind(resourceSelector.APIVersion, resourceSelector.Kind)) schema.FromAPIVersionAndKind(resourceSelector.APIVersion, resourceSelector.Kind))
if err != nil { if err != nil {
klog.Errorf("Failed to get GVR from GVK %s %s. Error: %v", resourceSelector.APIVersion, klog.Errorf("Failed to get GVR from GVK %s %s. Error: %v", resourceSelector.APIVersion,
resourceSelector.Kind, err) resourceSelector.Kind, err)
return err return nil, err
} }
workload, err := c.DynamicClient.Resource(dynamicResource).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}) workload, err := c.DynamicClient.Resource(dynamicResource).Namespace(resourceSelector.Namespace).Get(context.TODO(), resourceSelector.Name, metav1.GetOptions{})
if err != nil && !errors.IsNotFound(err) { if err != nil {
if errors.IsNotFound(err) {
klog.Warningf("Workload does not exist, kind: %s, namespace: %s, name: %s",
resourceSelector.Kind, resourceSelector.Namespace, resourceSelector.Name)
return nil, nil
}
klog.Errorf("Failed to get workload, kind: %s, namespace: %s, name: %s. Error: %v", klog.Errorf("Failed to get workload, kind: %s, namespace: %s, name: %s. Error: %v",
resourceSelector.Kind, namespace, name, err) resourceSelector.Kind, resourceSelector.Namespace, resourceSelector.Name, err)
return err return nil, err
} }
if err != nil && errors.IsNotFound(err) { if workload.GetDeletionTimestamp() != nil {
klog.Warningf("Workload is not exist, kind: %s, namespace: %s, name: %s", return nil, nil
resourceSelector.Kind, namespace, name)
continue
} }
if workload.GetDeletionTimestamp() == nil { return workload, nil
*workloads = append(*workloads, workload)
}
}
return nil
} }
// getTargetClusters get targetClusters by placement. // getTargetClusters get targetClusters by placement.

View File

@ -19,8 +19,7 @@ func NewPolicyWithSingleDeployment(namespace string, name string, deployment *ap
{ {
APIVersion: deployment.APIVersion, APIVersion: deployment.APIVersion,
Kind: deployment.Kind, Kind: deployment.Kind,
Names: []string{deployment.Name}, Name: deployment.Name,
Namespaces: []string{deployment.Namespace},
}, },
}, },
Placement: propagationapi.Placement{ Placement: propagationapi.Placement{