Skip to content

Commit e024751

Browse files
committed
feat: update the resource_selector to support RP
Signed-off-by: Zhiying Lin <[email protected]>
1 parent 9e0ca9e commit e024751

File tree

4 files changed

+999
-39
lines changed

4 files changed

+999
-39
lines changed

CLAUDE.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,16 @@ cmd/memberagent/ # Member agent main and setup
159159
## Testing Patterns
160160

161161
### Unit Tests
162-
- Use `testify` for assertions
162+
- Avoid the use of ‘assert’ libraries.
163163
- Controllers use `envtest` for integration testing with real etcd
164164
- Mock external dependencies with `gomock`
165165
- Unit test files: `<go_file>_test.go` in same directory
166166
- Table-driven test style preferred
167+
- Use cmp.Equal for equality comparison and cmp.Diff to obtain a human-readable diff between objects.
168+
- Test outputs should output the actual value that the function returned before printing the value that was expected. A usual format for printing test outputs is “YourFunc(%v) = %v, want %v”.
169+
- If your function returns a struct, don’t write test code that performs an individual comparison for each field of the struct. Instead, construct the struct that you’re expecting your function to return, and compare in one shot using diffs or deep comparisons. The same rule applies to arrays and maps.
170+
- If your struct needs to be compared for approximate equality or some other kind of semantic equality, or it contains fields that cannot be compared for equality (e.g. if one of the fields is an io.Reader), tweaking a cmp.Diff or cmp.Equal comparison with cmpopts options such as cmpopts.IgnoreInterfaces may meet your needs (example); otherwise, this technique just won’t work, so do whatever works.
171+
- If your function returns multiple return values, you don’t need to wrap those in a struct before comparing them. Just compare the return values individually and print them.
167172

168173
### Integration Tests
169174
- Located in `test/integration/` and `test/scheduler/`

pkg/controllers/clusterresourceplacement/resource_selector.go

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"k8s.io/apimachinery/pkg/labels"
3030
"k8s.io/apimachinery/pkg/runtime"
3131
"k8s.io/apimachinery/pkg/runtime/schema"
32+
"k8s.io/apimachinery/pkg/types"
3233
"k8s.io/klog/v2"
3334
"sigs.k8s.io/controller-runtime/pkg/client"
3435
workv1alpha1 "sigs.k8s.io/work-api/pkg/apis/v1alpha1"
@@ -83,7 +84,7 @@ var (
8384
// selectResources selects the resources according to the placement resourceSelectors.
8485
// It also generates an array of manifests obj based on the selected resources.
8586
func (r *Reconciler) selectResources(placement *fleetv1alpha1.ClusterResourcePlacement) ([]workv1alpha1.Manifest, error) {
86-
selectedObjects, err := r.gatherSelectedResource(placement.GetName(), convertResourceSelector(placement.Spec.ResourceSelectors))
87+
selectedObjects, err := r.gatherSelectedResource(types.NamespacedName{Name: placement.GetName()}, convertResourceSelector(placement.Spec.ResourceSelectors))
8788
if err != nil {
8889
return nil, err
8990
}
@@ -128,8 +129,7 @@ func convertResourceSelector(old []fleetv1alpha1.ClusterResourceSelector) []flee
128129
}
129130

130131
// gatherSelectedResource gets all the resources according to the resource selector.
131-
// TODO: treat the RP selector differently to not allow RP to select cluster scoped resources
132-
func (r *Reconciler) gatherSelectedResource(placement string, selectors []fleetv1beta1.ClusterResourceSelector) ([]*unstructured.Unstructured, error) {
132+
func (r *Reconciler) gatherSelectedResource(placementName types.NamespacedName, selectors []fleetv1beta1.ClusterResourceSelector) ([]*unstructured.Unstructured, error) {
133133
var resources []*unstructured.Unstructured
134134
var resourceMap = make(map[fleetv1beta1.ResourceIdentifier]bool)
135135
for _, selector := range selectors {
@@ -145,10 +145,10 @@ func (r *Reconciler) gatherSelectedResource(placement string, selectors []fleetv
145145
}
146146
var objs []runtime.Object
147147
var err error
148-
if gvk == utils.NamespaceGVK {
149-
objs, err = r.fetchNamespaceResources(selector, placement)
148+
if gvk == utils.NamespaceGVK && placementName.Namespace == "" {
149+
objs, err = r.fetchNamespaceResources(selector, placementName.Name)
150150
} else {
151-
objs, err = r.fetchClusterScopedResources(selector, placement)
151+
objs, err = r.fetchResources(selector, placementName)
152152
}
153153
if err != nil {
154154
return nil, err
@@ -164,7 +164,7 @@ func (r *Reconciler) gatherSelectedResource(placement string, selectors []fleetv
164164
}
165165
if _, exist := resourceMap[ri]; exist {
166166
err = fmt.Errorf("found duplicate resource %+v", ri)
167-
klog.ErrorS(err, "user selected one resource more than once", "resource", ri, "placement", placement)
167+
klog.ErrorS(err, "User selected one resource more than once", "resource", ri, "placement", placementName)
168168
return nil, controller.NewUserError(err)
169169
}
170170
resourceMap[ri] = true
@@ -230,43 +230,64 @@ func buildApplyOrderMap() map[string]int {
230230
return ordering
231231
}
232232

233-
// fetchClusterScopedResources retrieves the objects based on the selector.
234-
func (r *Reconciler) fetchClusterScopedResources(selector fleetv1beta1.ClusterResourceSelector, placeName string) ([]runtime.Object, error) {
235-
klog.V(2).InfoS("start to fetch the cluster scoped resources by the selector", "selector", selector)
233+
// fetchResources retrieves the objects based on the selector.
234+
func (r *Reconciler) fetchResources(selector fleetv1beta1.ClusterResourceSelector, placementName types.NamespacedName) ([]runtime.Object, error) {
235+
klog.V(2).InfoS("Start to fetch resources by the selector", "selector", selector, "placement", placementName)
236236
gk := schema.GroupKind{
237237
Group: selector.Group,
238238
Kind: selector.Kind,
239239
}
240240
restMapping, err := r.RestMapper.RESTMapping(gk, selector.Version)
241241
if err != nil {
242-
return nil, controller.NewUserError(fmt.Errorf("invalid placement %s, failed to get GVR of the selector: %w", placeName, err))
242+
return nil, controller.NewUserError(fmt.Errorf("invalid placement %s, failed to get GVR of the selector: %w", placementName, err))
243243
}
244244
gvr := restMapping.Resource
245245
gvk := schema.GroupVersionKind{
246246
Group: selector.Group,
247247
Version: selector.Version,
248248
Kind: selector.Kind,
249249
}
250-
if !r.InformerManager.IsClusterScopedResources(gvk) {
251-
return nil, controller.NewUserError(fmt.Errorf("invalid placement %s: %+v is not a cluster scoped resource", placeName, restMapping.Resource))
250+
251+
isNamespacedResource := !r.InformerManager.IsClusterScopedResources(gvk)
252+
if isNamespacedResource && placementName.Namespace == "" {
253+
// If it's a namespace-scoped resource but placement has no namespace, return error.
254+
err := fmt.Errorf("invalid placement %s: cannot select namespace-scoped resource %v without a namespace", placementName, gvr)
255+
klog.ErrorS(err, "Invalid resource selector", "selector", selector)
256+
return nil, controller.NewUserError(err)
257+
} else if !isNamespacedResource && placementName.Namespace != "" {
258+
// If it's a cluster-scoped resource but placement has a namespace, return error.
259+
err := fmt.Errorf("invalid placement %s: cannot select cluster-scoped resource %v with a namespace", placementName, gvr)
260+
klog.ErrorS(err, "Invalid resource selector", "selector", selector)
261+
return nil, controller.NewUserError(err)
252262
}
263+
253264
if !r.InformerManager.IsInformerSynced(gvr) {
254-
return nil, controller.NewExpectedBehaviorError(fmt.Errorf("informer cache for %+v is not synced yet", restMapping.Resource))
265+
err := fmt.Errorf("informer cache for %+v is not synced yet", restMapping.Resource)
266+
klog.ErrorS(err, "Informer cache is not synced", "gvr", gvr, "placement", placementName)
267+
return nil, controller.NewExpectedBehaviorError(err)
255268
}
256269

257270
lister := r.InformerManager.Lister(gvr)
271+
258272
// TODO: validator should enforce the mutual exclusiveness between the `name` and `labelSelector` fields
259273
if len(selector.Name) != 0 {
260-
obj, err := lister.Get(selector.Name)
274+
var obj runtime.Object
275+
var err error
276+
277+
if isNamespacedResource {
278+
obj, err = lister.ByNamespace(placementName.Namespace).Get(selector.Name)
279+
} else {
280+
obj, err = lister.Get(selector.Name)
281+
}
282+
261283
if err != nil {
262-
klog.ErrorS(err, "cannot get the resource", "gvr", gvr, "name", selector.Name)
284+
klog.ErrorS(err, "Cannot get the resource", "gvr", gvr, "name", selector.Name, "namespace", placementName.Namespace)
263285
return nil, controller.NewAPIServerError(true, client.IgnoreNotFound(err))
264286
}
265-
uObj := obj.DeepCopyObject().(*unstructured.Unstructured)
266-
if uObj.GetDeletionTimestamp() != nil {
267-
// skip a to be deleted namespace
268-
klog.V(2).InfoS("skip the deleting cluster scoped resources by the selector",
269-
"selector", selector, "placeName", placeName, "resource name", uObj.GetName())
287+
if uObj := obj.DeepCopyObject().(*unstructured.Unstructured); uObj.GetDeletionTimestamp() != nil {
288+
// skip a to be deleted resource
289+
klog.V(2).InfoS("Skip the deleting resource by the selector",
290+
"selector", selector, "placement", placementName, "resourceName", uObj.GetName())
270291
return []runtime.Object{}, nil
271292
}
272293
return []runtime.Object{obj}, nil
@@ -282,18 +303,26 @@ func (r *Reconciler) fetchClusterScopedResources(selector fleetv1beta1.ClusterRe
282303
return nil, controller.NewUnexpectedBehaviorError(fmt.Errorf("cannot convert the label selector to a selector: %w", err))
283304
}
284305
}
306+
285307
var selectedObjs []runtime.Object
286-
objects, err := lister.List(labelSelector)
308+
var objects []runtime.Object
309+
310+
if isNamespacedResource {
311+
objects, err = lister.ByNamespace(placementName.Namespace).List(labelSelector)
312+
} else {
313+
objects, err = lister.List(labelSelector)
314+
}
287315
if err != nil {
288-
return nil, controller.NewAPIServerError(true, fmt.Errorf("cannot list all the objects: %w", err))
316+
klog.ErrorS(err, "Cannot list all the objects", "gvr", gvr, "labelSelector", labelSelector, "placement", placementName)
317+
return nil, controller.NewAPIServerError(true, err)
289318
}
319+
290320
// go ahead and claim all objects by adding a finalizer and insert the placement in its annotation
291321
for i := 0; i < len(objects); i++ {
292-
uObj := objects[i].DeepCopyObject().(*unstructured.Unstructured)
293-
if uObj.GetDeletionTimestamp() != nil {
294-
// skip a to be deleted namespace
295-
klog.V(2).InfoS("skip the deleting cluster scoped resources by the selector",
296-
"selector", selector, "placeName", placeName, "resource name", uObj.GetName())
322+
if uObj := objects[i].DeepCopyObject().(*unstructured.Unstructured); uObj.GetDeletionTimestamp() != nil {
323+
// skip a to be deleted resource
324+
klog.V(2).InfoS("skip the deleting resource by the selector",
325+
"selector", selector, "placement", placementName, "resourceName", uObj.GetName())
297326
continue
298327
}
299328
selectedObjs = append(selectedObjs, objects[i])
@@ -330,7 +359,8 @@ func (r *Reconciler) fetchNamespaceResources(selector fleetv1beta1.ClusterResour
330359
}
331360
namespaces, err := r.InformerManager.Lister(utils.NamespaceGVR).List(labelSelector)
332361
if err != nil {
333-
return nil, controller.NewAPIServerError(true, fmt.Errorf("cannot list all the namespaces given the label selector: %w", err))
362+
klog.ErrorS(err, "Cannot list all the namespaces by the label selector", "labelSelector", labelSelector, "placement", placeName)
363+
return nil, controller.NewAPIServerError(true, err)
334364
}
335365

336366
for _, namespace := range namespaces {
@@ -384,10 +414,17 @@ func (r *Reconciler) fetchAllResourcesInOneNamespace(namespaceName string, place
384414
lister := r.InformerManager.Lister(gvr)
385415
objs, err := lister.ByNamespace(namespaceName).List(labels.Everything())
386416
if err != nil {
387-
return nil, controller.NewAPIServerError(true, fmt.Errorf("cannot list all the objects of type %+v in namespace %s: %w", gvr, namespaceName, err))
417+
klog.ErrorS(err, "Cannot list all the objects in namespace", "gvr", gvr, "namespace", namespaceName)
418+
return nil, controller.NewAPIServerError(true, err)
388419
}
389420
for _, obj := range objs {
390421
uObj := obj.DeepCopyObject().(*unstructured.Unstructured)
422+
if uObj.GetDeletionTimestamp() != nil {
423+
// skip a to be deleted resource
424+
klog.V(2).InfoS("skip the deleting resource by the selector",
425+
"placeName", placeName, "namespace", namespaceName, "object", klog.KObj(uObj))
426+
continue
427+
}
391428
shouldInclude, err := utils.ShouldPropagateObj(r.InformerManager, uObj)
392429
if err != nil {
393430
klog.ErrorS(err, "cannot determine if we should propagate an object", "object", klog.KObj(uObj))
@@ -520,8 +557,10 @@ func generateResourceContent(object *unstructured.Unstructured) (*fleetv1beta1.R
520557
// It also returns the number of envelope configmaps so the CRP controller can have the right expectation of the number of work objects.
521558
func (r *Reconciler) selectResourcesForPlacement(placementObj fleetv1beta1.PlacementObj) (int, []fleetv1beta1.ResourceContent, []fleetv1beta1.ResourceIdentifier, error) {
522559
envelopeObjCount := 0
523-
placementSpec := placementObj.GetPlacementSpec()
524-
selectedObjects, err := r.gatherSelectedResource(placementObj.GetName(), placementSpec.ResourceSelectors)
560+
selectedObjects, err := r.gatherSelectedResource(types.NamespacedName{
561+
Name: placementObj.GetName(),
562+
Namespace: placementObj.GetNamespace(),
563+
}, placementObj.GetPlacementSpec().ResourceSelectors)
525564
if err != nil {
526565
return 0, nil, nil, err
527566
}

0 commit comments

Comments
 (0)