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. type: string
items: namespace:
type: string description: Namespace of the target resource. Default is empty,
type: array which means inherit from the parent object scope.
namespaces: type: string
description: 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.
items:
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. type: string
items: namespace:
type: string description: Namespace of the target resource. Default is empty,
type: array which means inherit from the parent object scope.
namespaces: type: string
description: 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.
items:
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 {
if resourceSelector.LabelSelector == nil {
err := c.fetchWorkloadsWithOutLabelSelector(resourceSelector, namespace, names, &workloads)
if err != nil {
klog.Errorf("Failed to fetch workloads by names in namespace %s. Error: %v.", namespace, err)
return nil, err
}
} else {
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
}
}
} }
tmpWorkloads, err := c.fetchWorkloadsByResourceSelector(resourceSelector)
if err != nil {
klog.Errorf("Failed to fetch workloads with labelSelector in namespace %s. Error: %v.", policy.Namespace, err)
return nil, err
}
workloads = append(workloads, tmpWorkloads...)
} }
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 {
for _, unstructuredWorkLoad := range unstructuredWorkLoadList.Items { var workloads []*unstructured.Unstructured
if unstructuredWorkLoad.GetDeletionTimestamp() == nil { for _, unstructuredWorkLoad := range unstructuredWorkLoadList.Items {
*workloads = append(*workloads, &unstructuredWorkLoad) if unstructuredWorkLoad.GetDeletionTimestamp() == nil {
} workloads = append(workloads, &unstructuredWorkLoad)
}
} else {
for _, unstructuredWorkLoad := range unstructuredWorkLoadList.Items {
for _, name := range names {
if unstructuredWorkLoad.GetName() == name && unstructuredWorkLoad.GetDeletionTimestamp() == nil {
*workloads = append(*workloads, &unstructuredWorkLoad)
break
}
}
} }
} }
return nil
return workloads, 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 { dynamicResource, err := restmapper.GetGroupVersionResource(c.RESTMapper,
for _, name := range names { schema.FromAPIVersionAndKind(resourceSelector.APIVersion, resourceSelector.Kind))
dynamicResource, err := restmapper.GetGroupVersionResource(c.RESTMapper, if err != nil {
schema.FromAPIVersionAndKind(resourceSelector.APIVersion, resourceSelector.Kind)) klog.Errorf("Failed to get GVR from GVK %s %s. Error: %v", resourceSelector.APIVersion,
if err != nil { resourceSelector.Kind, err)
klog.Errorf("Failed to get GVR from GVK %s %s. Error: %v", resourceSelector.APIVersion, return nil, err
resourceSelector.Kind, err)
return err
}
workload, err := c.DynamicClient.Resource(dynamicResource).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil && !errors.IsNotFound(err) {
klog.Errorf("Failed to get workload, kind: %s, namespace: %s, name: %s. Error: %v",
resourceSelector.Kind, namespace, name, err)
return err
}
if err != nil && errors.IsNotFound(err) {
klog.Warningf("Workload is not exist, kind: %s, namespace: %s, name: %s",
resourceSelector.Kind, namespace, name)
continue
}
if workload.GetDeletionTimestamp() == nil {
*workloads = append(*workloads, workload)
}
} }
return nil workload, err := c.DynamicClient.Resource(dynamicResource).Namespace(resourceSelector.Namespace).Get(context.TODO(), resourceSelector.Name, metav1.GetOptions{})
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",
resourceSelector.Kind, resourceSelector.Namespace, resourceSelector.Name, err)
return nil, err
}
if workload.GetDeletionTimestamp() != nil {
return nil, nil
}
return workload, 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{