Skip to content

Commit 0dad88b

Browse files
feat: block the duplicated resource select (#14)
1 parent 4e5ea3c commit 0dad88b

File tree

6 files changed

+367
-42
lines changed

6 files changed

+367
-42
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ CONTROLLER_GEN_VER := v0.16.0
3131
CONTROLLER_GEN_BIN := controller-gen
3232
CONTROLLER_GEN := $(abspath $(TOOLS_BIN_DIR)/$(CONTROLLER_GEN_BIN)-$(CONTROLLER_GEN_VER))
3333

34-
STATICCHECK_VER := 2024.1
34+
STATICCHECK_VER := 2025.1
3535
STATICCHECK_BIN := staticcheck
3636
STATICCHECK := $(abspath $(TOOLS_BIN_DIR)/$(STATICCHECK_BIN)-$(STATICCHECK_VER))
3737

pkg/controllers/clusterresourceplacement/controller.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ import (
5151
// if object size is greater than 1MB https://github.com/kubernetes/kubernetes/blob/db1990f48b92d603f469c1c89e2ad36da1b74846/test/integration/master/synthetic_master_test.go#L337
5252
var resourceSnapshotResourceSizeLimit = 800 * (1 << 10) // 800KB
5353

54+
// We use a safety resync period to requeue all the finished request just in case there is a bug in the system.
55+
// TODO: unify all the controllers with this pattern and make this configurable in place of the controller runtime resync period.
56+
const controllerResyncPeriod = 15 * time.Minute
57+
5458
func (r *Reconciler) Reconcile(ctx context.Context, key controller.QueueKey) (ctrl.Result, error) {
5559
name, ok := key.(string)
5660
if !ok {
@@ -188,7 +192,8 @@ func (r *Reconciler) handleUpdate(ctx context.Context, crp *fleetv1beta1.Cluster
188192
klog.ErrorS(updateErr, "Failed to update the status", "clusterResourcePlacement", crpKObj)
189193
return ctrl.Result{}, controller.NewUpdateIgnoreConflictError(updateErr)
190194
}
191-
return ctrl.Result{}, err
195+
// no need to retry faster, the user needs to fix the resource selectors
196+
return ctrl.Result{RequeueAfter: controllerResyncPeriod}, nil
192197
}
193198

194199
latestSchedulingPolicySnapshot, err := r.getOrCreateClusterSchedulingPolicySnapshot(ctx, crp, int(revisionLimit))
@@ -250,11 +255,11 @@ func (r *Reconciler) handleUpdate(ctx context.Context, crp *fleetv1beta1.Cluster
250255
// Here we requeue the request to prevent a bug in the watcher.
251256
klog.V(2).InfoS("Scheduler has not scheduled any cluster yet and requeue the request as a backup",
252257
"clusterResourcePlacement", crpKObj, "scheduledCondition", crp.GetCondition(string(fleetv1beta1.ClusterResourcePlacementScheduledConditionType)), "generation", crp.Generation)
253-
return ctrl.Result{RequeueAfter: 5 * time.Minute}, nil
258+
return ctrl.Result{RequeueAfter: controllerResyncPeriod}, nil
254259
}
255260
klog.V(2).InfoS("Placement rollout has not finished yet and requeue the request", "clusterResourcePlacement", crpKObj, "status", crp.Status, "generation", crp.Generation)
256261
// no need to requeue the request as the binding status will be changed but we add a long resync loop just in case.
257-
return ctrl.Result{RequeueAfter: 5 * time.Minute}, nil
262+
return ctrl.Result{RequeueAfter: controllerResyncPeriod}, nil
258263
}
259264

260265
func (r *Reconciler) getOrCreateClusterSchedulingPolicySnapshot(ctx context.Context, crp *fleetv1beta1.ClusterResourcePlacement, revisionHistoryLimit int) (*fleetv1beta1.ClusterSchedulingPolicySnapshot, error) {

pkg/controllers/clusterresourceplacement/resource_selector.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ func (r *Reconciler) selectResources(placement *fleetv1alpha1.ClusterResourcePla
4848
}
4949
placement.Status.SelectedResources = make([]fleetv1alpha1.ResourceIdentifier, 0)
5050
manifests := make([]workv1alpha1.Manifest, len(selectedObjects))
51-
for i, obj := range selectedObjects {
52-
unstructuredObj := obj.DeepCopyObject().(*unstructured.Unstructured)
51+
for i, unstructuredObj := range selectedObjects {
5352
gvk := unstructuredObj.GroupVersionKind()
5453
res := fleetv1alpha1.ResourceIdentifier{
5554
Group: gvk.Group,
@@ -81,8 +80,9 @@ func convertResourceSelector(old []fleetv1alpha1.ClusterResourceSelector) []flee
8180
}
8281

8382
// gatherSelectedResource gets all the resources according to the resource selector.
84-
func (r *Reconciler) gatherSelectedResource(placement string, selectors []fleetv1beta1.ClusterResourceSelector) ([]runtime.Object, error) {
85-
var resources []runtime.Object
83+
func (r *Reconciler) gatherSelectedResource(placement string, selectors []fleetv1beta1.ClusterResourceSelector) ([]*unstructured.Unstructured, error) {
84+
var resources []*unstructured.Unstructured
85+
var resourceMap = make(map[fleetv1beta1.ResourceIdentifier]bool)
8686
for _, selector := range selectors {
8787
gvk := schema.GroupVersionKind{
8888
Group: selector.Group,
@@ -104,7 +104,23 @@ func (r *Reconciler) gatherSelectedResource(placement string, selectors []fleetv
104104
if err != nil {
105105
return nil, err
106106
}
107-
resources = append(resources, objs...)
107+
for _, obj := range objs {
108+
uObj := obj.(*unstructured.Unstructured)
109+
ri := fleetv1beta1.ResourceIdentifier{
110+
Group: obj.GetObjectKind().GroupVersionKind().Group,
111+
Version: obj.GetObjectKind().GroupVersionKind().Version,
112+
Kind: obj.GetObjectKind().GroupVersionKind().Kind,
113+
Name: uObj.GetName(),
114+
Namespace: uObj.GetNamespace(),
115+
}
116+
if _, exist := resourceMap[ri]; exist {
117+
err = fmt.Errorf("found duplicate resource %+v", ri)
118+
klog.ErrorS(err, "user selected one resource more than once", "resource", ri, "placement", placement)
119+
return nil, controller.NewUserError(err)
120+
}
121+
resourceMap[ri] = true
122+
resources = append(resources, uObj)
123+
}
108124
}
109125
// sort the resources in strict order so that we will get the stable list of manifest so that
110126
// the generated work object doesn't change between reconcile loops
@@ -113,16 +129,16 @@ func (r *Reconciler) gatherSelectedResource(placement string, selectors []fleetv
113129
return resources, nil
114130
}
115131

116-
func sortResources(resources []runtime.Object) {
132+
func sortResources(resources []*unstructured.Unstructured) {
117133
sort.Slice(resources, func(i, j int) bool {
118-
obj1 := resources[i].DeepCopyObject().(*unstructured.Unstructured)
119-
obj2 := resources[j].DeepCopyObject().(*unstructured.Unstructured)
134+
obj1 := resources[i]
135+
obj2 := resources[j]
120136
gvk1 := obj1.GetObjectKind().GroupVersionKind().String()
121137
gvk2 := obj2.GetObjectKind().GroupVersionKind().String()
122138
// compare group/version;kind for the rest of type of resources
123139
gvkComp := strings.Compare(gvk1, gvk2)
124140
if gvkComp == 0 {
125-
// same gvk, compare namespace/name
141+
// same gvk, compare namespace/name, no duplication exists
126142
return strings.Compare(fmt.Sprintf("%s/%s", obj1.GetNamespace(), obj1.GetName()),
127143
fmt.Sprintf("%s/%s", obj2.GetNamespace(), obj2.GetName())) > 0
128144
}
@@ -442,8 +458,7 @@ func (r *Reconciler) selectResourcesForPlacement(placement *fleetv1beta1.Cluster
442458

443459
resources := make([]fleetv1beta1.ResourceContent, len(selectedObjects))
444460
resourcesIDs := make([]fleetv1beta1.ResourceIdentifier, len(selectedObjects))
445-
for i, obj := range selectedObjects {
446-
unstructuredObj := obj.DeepCopyObject().(*unstructured.Unstructured)
461+
for i, unstructuredObj := range selectedObjects {
447462
rc, err := generateResourceContent(unstructuredObj)
448463
if err != nil {
449464
return 0, nil, nil, err

pkg/controllers/clusterresourceplacement/resource_selector_test.go

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -935,52 +935,52 @@ func TestSortResource(t *testing.T) {
935935
}
936936

937937
tests := map[string]struct {
938-
resources []runtime.Object
939-
want []runtime.Object
938+
resources []*unstructured.Unstructured
939+
want []*unstructured.Unstructured
940940
}{
941941
"should handle empty resources list": {
942-
resources: []runtime.Object{},
943-
want: []runtime.Object{},
942+
resources: []*unstructured.Unstructured{},
943+
want: []*unstructured.Unstructured{},
944944
},
945945
"should handle single resource": {
946-
resources: []runtime.Object{deployment},
947-
want: []runtime.Object{deployment},
946+
resources: []*unstructured.Unstructured{deployment},
947+
want: []*unstructured.Unstructured{deployment},
948948
},
949949
"should handle multiple namespaces": {
950-
resources: []runtime.Object{namespace1, namespace2},
951-
want: []runtime.Object{namespace2, namespace1},
950+
resources: []*unstructured.Unstructured{namespace1, namespace2},
951+
want: []*unstructured.Unstructured{namespace2, namespace1},
952952
},
953953
"should gather selected resources with Namespace in front with order": {
954-
resources: []runtime.Object{deployment, namespace1, namespace2},
955-
want: []runtime.Object{namespace2, namespace1, deployment},
954+
resources: []*unstructured.Unstructured{deployment, namespace1, namespace2},
955+
want: []*unstructured.Unstructured{namespace2, namespace1, deployment},
956956
},
957957
"should gather selected resources with CRD in front with order": {
958-
resources: []runtime.Object{clusterRole, crd1, crd2},
959-
want: []runtime.Object{crd2, crd1, clusterRole},
958+
resources: []*unstructured.Unstructured{clusterRole, crd1, crd2},
959+
want: []*unstructured.Unstructured{crd2, crd1, clusterRole},
960960
},
961961
"should gather selected resources with CRD or Namespace in front with order": {
962-
resources: []runtime.Object{deployment, namespace1, namespace2, clusterRole, crd1, crd2},
963-
want: []runtime.Object{namespace2, namespace1, crd2, crd1, clusterRole, deployment},
962+
resources: []*unstructured.Unstructured{deployment, namespace1, namespace2, clusterRole, crd1, crd2},
963+
want: []*unstructured.Unstructured{namespace2, namespace1, crd2, crd1, clusterRole, deployment},
964964
},
965965
"should gather selected resources with CRD or Namespace in front with order, second case": {
966-
resources: []runtime.Object{crd1, crd2, deployment, namespace2, clusterRole},
967-
want: []runtime.Object{namespace2, crd2, crd1, deployment, clusterRole},
966+
resources: []*unstructured.Unstructured{crd1, crd2, deployment, namespace2, clusterRole},
967+
want: []*unstructured.Unstructured{namespace2, crd2, crd1, deployment, clusterRole},
968968
},
969969
"should gather selected resources with PersistentVolumeClaim in front with order": {
970-
resources: []runtime.Object{deployment, pvc, namespace1, role},
971-
want: []runtime.Object{namespace1, pvc, deployment, role},
970+
resources: []*unstructured.Unstructured{deployment, pvc, namespace1, role},
971+
want: []*unstructured.Unstructured{namespace1, pvc, deployment, role},
972972
},
973973
"should gather selected resources with Secret in front with order": {
974-
resources: []runtime.Object{deployment, secret, namespace1, crd1, namespace2, role},
975-
want: []runtime.Object{namespace2, namespace1, crd1, secret, deployment, role},
974+
resources: []*unstructured.Unstructured{deployment, secret, namespace1, crd1, namespace2, role},
975+
want: []*unstructured.Unstructured{namespace2, namespace1, crd1, secret, deployment, role},
976976
},
977977
"should gather selected resources with ConfigMap and Secret in front with order": {
978-
resources: []runtime.Object{deployment, secret, namespace1, role, configMap, secret2},
979-
want: []runtime.Object{namespace1, configMap, secret2, secret, deployment, role},
978+
resources: []*unstructured.Unstructured{deployment, secret, namespace1, role, configMap, secret2},
979+
want: []*unstructured.Unstructured{namespace1, configMap, secret2, secret, deployment, role},
980980
},
981981
"should gather selected all the resources with the right order": {
982-
resources: []runtime.Object{configMap, deployment, role, crd1, pvc, secret2, clusterRole, secret, namespace1, namespace2, crd2},
983-
want: []runtime.Object{namespace2, namespace1, crd2, crd1, configMap, secret2, secret, pvc, deployment, clusterRole, role},
982+
resources: []*unstructured.Unstructured{configMap, deployment, role, crd1, pvc, secret2, clusterRole, secret, namespace1, namespace2, crd2},
983+
want: []*unstructured.Unstructured{namespace2, namespace1, crd2, crd1, configMap, secret2, secret, pvc, deployment, clusterRole, role},
984984
},
985985
}
986986

0 commit comments

Comments
 (0)