Skip to content

Commit a2e4bf8

Browse files
authored
feat: resource selection struct and selection related functions (kubefleet-dev#418)
1 parent 80e3bf3 commit a2e4bf8

6 files changed

Lines changed: 2841 additions & 293 deletions

File tree

pkg/controllers/placement/resource_selector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ func (r *Reconciler) shouldPropagateObj(namespace, placementName string, obj run
294294
return false, nil
295295
}
296296

297-
shouldInclude, err := utils.ShouldPropagateObj(r.InformerManager, uObj, r.EnableWorkload)
297+
shouldInclude, err := controller.ShouldPropagateObj(r.InformerManager, uObj, r.EnableWorkload)
298298
if err != nil {
299299
klog.ErrorS(err, "Cannot determine if we should propagate an object", "namespace", namespace, "placement", placementName, "object", uObjKObj)
300300
return false, err

pkg/resourcewatcher/change_dector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func (d *ChangeDetector) dynamicResourceFilter(obj interface{}) bool {
168168
}
169169

170170
if unstructuredObj, ok := obj.(*unstructured.Unstructured); ok {
171-
shouldPropagate, err := utils.ShouldPropagateObj(d.InformerManager, unstructuredObj.DeepCopy(), d.EnableWorkload)
171+
shouldPropagate, err := controller.ShouldPropagateObj(d.InformerManager, unstructuredObj.DeepCopy(), d.EnableWorkload)
172172
if err != nil || !shouldPropagate {
173173
klog.V(5).InfoS("Skip watching resource in namespace", "namespace", cwKey.Namespace,
174174
"group", cwKey.Group, "version", cwKey.Version, "kind", cwKey.Kind, "object", cwKey.Name)

pkg/utils/common.go

Lines changed: 1 addition & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,14 @@ import (
2727
appv1 "k8s.io/api/apps/v1"
2828
batchv1 "k8s.io/api/batch/v1"
2929
corev1 "k8s.io/api/core/v1"
30-
discoveryv1 "k8s.io/api/discovery/v1"
3130
networkingv1 "k8s.io/api/networking/v1"
3231
policyv1 "k8s.io/api/policy/v1"
3332
rbacv1 "k8s.io/api/rbac/v1"
3433
schedulingv1 "k8s.io/api/scheduling/v1"
3534
storagev1 "k8s.io/api/storage/v1"
3635
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3736
"k8s.io/apimachinery/pkg/api/equality"
38-
apierrors "k8s.io/apimachinery/pkg/api/errors"
3937
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
40-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
41-
"k8s.io/apimachinery/pkg/runtime"
4238
"k8s.io/apimachinery/pkg/runtime/schema"
4339
"k8s.io/client-go/discovery"
4440
"k8s.io/client-go/util/retry"
@@ -50,8 +46,6 @@ import (
5046
placementv1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1"
5147
placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1"
5248
"github.com/kubefleet-dev/kubefleet/pkg/utils/condition"
53-
"github.com/kubefleet-dev/kubefleet/pkg/utils/controller"
54-
"github.com/kubefleet-dev/kubefleet/pkg/utils/informer"
5549
)
5650

5751
const (
@@ -505,71 +499,6 @@ func CheckCRDInstalled(discoveryClient discovery.DiscoveryInterface, gvk schema.
505499
return err
506500
}
507501

508-
// ShouldPropagateObj decides if one should propagate the object.
509-
// PVCs are only propagated when enableWorkload is false (workloads not allowed on hub).
510-
func ShouldPropagateObj(informerManager informer.Manager, uObj *unstructured.Unstructured, enableWorkload bool) (bool, error) {
511-
// TODO: add more special handling for different resource kind
512-
switch uObj.GroupVersionKind() {
513-
case appv1.SchemeGroupVersion.WithKind(ReplicaSetKind):
514-
// Skip ReplicaSets if they are managed by Deployments (have owner references).
515-
// Standalone ReplicaSets (without owners) can be propagated.
516-
if len(uObj.GetOwnerReferences()) > 0 {
517-
return false, nil
518-
}
519-
case appv1.SchemeGroupVersion.WithKind("ControllerRevision"):
520-
// Skip ControllerRevisions if they are managed by DaemonSets/StatefulSets (have owner references).
521-
// Standalone ControllerRevisions (without owners) can be propagated.
522-
if len(uObj.GetOwnerReferences()) > 0 {
523-
return false, nil
524-
}
525-
case corev1.SchemeGroupVersion.WithKind(ConfigMapKind):
526-
// Skip the built-in custom CA certificate created in the namespace.
527-
if uObj.GetName() == "kube-root-ca.crt" {
528-
return false, nil
529-
}
530-
case corev1.SchemeGroupVersion.WithKind("ServiceAccount"):
531-
// Skip the default service account created in the namespace.
532-
if uObj.GetName() == "default" {
533-
return false, nil
534-
}
535-
case corev1.SchemeGroupVersion.WithKind("Secret"):
536-
// The secret, with type 'kubernetes.io/service-account-token', is created along with `ServiceAccount` should be
537-
// prevented from propagating.
538-
var secret corev1.Secret
539-
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.Object, &secret); err != nil {
540-
return false, controller.NewUnexpectedBehaviorError(fmt.Errorf("failed to convert a secret object %s in namespace %s: %w", uObj.GetName(), uObj.GetNamespace(), err))
541-
}
542-
if secret.Type == corev1.SecretTypeServiceAccountToken {
543-
return false, nil
544-
}
545-
case corev1.SchemeGroupVersion.WithKind("PersistentVolumeClaim"):
546-
// Skip PersistentVolumeClaims by default to avoid conflicts with the PVCs created by statefulset controller.
547-
// This only happens if the workloads are allowed to run on the hub cluster.
548-
if enableWorkload {
549-
return false, nil
550-
}
551-
case corev1.SchemeGroupVersion.WithKind("Endpoints"):
552-
// we assume that all endpoints with the same name of a service is created by the service controller
553-
if _, err := informerManager.Lister(ServiceGVR).ByNamespace(uObj.GetNamespace()).Get(uObj.GetName()); err != nil {
554-
if apierrors.IsNotFound(err) {
555-
// there is no service of the same name as the end point,
556-
// we assume that this endpoint is created by the user
557-
return true, nil
558-
}
559-
return false, controller.NewAPIServerError(true, fmt.Errorf("failed to get the service %s in namespace %s: %w", uObj.GetName(), uObj.GetNamespace(), err))
560-
}
561-
// we find a service of the same name as the endpoint, we assume it's created by the service
562-
return false, nil
563-
case discoveryv1.SchemeGroupVersion.WithKind("EndpointSlice"):
564-
// all EndpointSlice created by the EndpointSlice controller has a managed by label
565-
if _, exist := uObj.GetLabels()[discoveryv1.LabelManagedBy]; exist {
566-
// do not propagate hub cluster generated endpoint slice
567-
return false, nil
568-
}
569-
}
570-
return true, nil
571-
}
572-
573502
// IsReservedNamespace indicates if an argued namespace is reserved.
574503
func IsReservedNamespace(namespace string) bool {
575504
return strings.HasPrefix(namespace, fleetPrefix) || strings.HasPrefix(namespace, kubePrefix)
@@ -774,7 +703,7 @@ var LessFuncDiffedResourcePlacementsV1 = func(a, b placementv1.DiffedResourcePla
774703
return aStr < bStr
775704
}
776705

777-
// LessFuncCondition is a less function for sorting conditions based on its types.
706+
// LessFuncConditionByType is a less function for sorting conditions based on its types.
778707
var LessFuncConditionByType = func(a, b metav1.Condition) bool {
779708
return a.Type < b.Type
780709
}

pkg/utils/common_test.go

Lines changed: 0 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66

77
"github.com/google/go-cmp/cmp"
88
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
109
"k8s.io/utils/ptr"
1110

1211
fleetv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1"
@@ -1190,221 +1189,3 @@ func TestIsDiffedResourcePlacementEqual(t *testing.T) {
11901189
})
11911190
}
11921191
}
1193-
1194-
func TestShouldPropagateObj(t *testing.T) {
1195-
tests := []struct {
1196-
name string
1197-
obj map[string]interface{}
1198-
ownerReferences []metav1.OwnerReference
1199-
enableWorkload bool
1200-
want bool
1201-
}{
1202-
{
1203-
name: "standalone replicaset without ownerReferences should propagate",
1204-
obj: map[string]interface{}{
1205-
"apiVersion": "apps/v1",
1206-
"kind": "ReplicaSet",
1207-
"metadata": map[string]interface{}{
1208-
"name": "standalone-rs",
1209-
"namespace": "default",
1210-
},
1211-
},
1212-
ownerReferences: nil,
1213-
enableWorkload: true,
1214-
want: true,
1215-
},
1216-
{
1217-
name: "standalone replicaset without ownerReferences should propagate if workload is disabled",
1218-
obj: map[string]interface{}{
1219-
"apiVersion": "apps/v1",
1220-
"kind": "ReplicaSet",
1221-
"metadata": map[string]interface{}{
1222-
"name": "standalone-rs",
1223-
"namespace": "default",
1224-
},
1225-
},
1226-
ownerReferences: nil,
1227-
enableWorkload: false,
1228-
want: true,
1229-
},
1230-
{
1231-
name: "standalone pod without ownerReferences should propagate",
1232-
obj: map[string]interface{}{
1233-
"apiVersion": "v1",
1234-
"kind": "Pod",
1235-
"metadata": map[string]interface{}{
1236-
"name": "standalone-pod",
1237-
"namespace": "default",
1238-
},
1239-
},
1240-
ownerReferences: nil,
1241-
enableWorkload: true,
1242-
want: true,
1243-
},
1244-
{
1245-
name: "replicaset with deployment owner should NOT propagate",
1246-
obj: map[string]interface{}{
1247-
"apiVersion": "apps/v1",
1248-
"kind": "ReplicaSet",
1249-
"metadata": map[string]interface{}{
1250-
"name": "test-deploy-abc123",
1251-
"namespace": "default",
1252-
},
1253-
},
1254-
ownerReferences: []metav1.OwnerReference{
1255-
{
1256-
APIVersion: "apps/v1",
1257-
Kind: "Deployment",
1258-
Name: "test-deploy",
1259-
UID: "12345",
1260-
},
1261-
},
1262-
enableWorkload: true,
1263-
want: false,
1264-
},
1265-
{
1266-
name: "pod owned by replicaset - passes ShouldPropagateObj but filtered by resource config",
1267-
obj: map[string]interface{}{
1268-
"apiVersion": "v1",
1269-
"kind": "Pod",
1270-
"metadata": map[string]interface{}{
1271-
"name": "test-deploy-abc123-xyz",
1272-
"namespace": "default",
1273-
},
1274-
},
1275-
ownerReferences: []metav1.OwnerReference{
1276-
{
1277-
APIVersion: "apps/v1",
1278-
Kind: "ReplicaSet",
1279-
Name: "test-deploy-abc123",
1280-
UID: "67890",
1281-
},
1282-
},
1283-
enableWorkload: false,
1284-
want: true, // ShouldPropagateObj doesn't filter Pods - they're filtered by NewResourceConfig
1285-
},
1286-
{
1287-
name: "controllerrevision owned by daemonset should NOT propagate",
1288-
obj: map[string]interface{}{
1289-
"apiVersion": "apps/v1",
1290-
"kind": "ControllerRevision",
1291-
"metadata": map[string]interface{}{
1292-
"name": "test-ds-7b9848797f",
1293-
"namespace": "default",
1294-
},
1295-
},
1296-
ownerReferences: []metav1.OwnerReference{
1297-
{
1298-
APIVersion: "apps/v1",
1299-
Kind: "DaemonSet",
1300-
Name: "test-ds",
1301-
UID: "abcdef",
1302-
},
1303-
},
1304-
enableWorkload: false,
1305-
want: false,
1306-
},
1307-
{
1308-
name: "controllerrevision owned by statefulset should NOT propagate",
1309-
obj: map[string]interface{}{
1310-
"apiVersion": "apps/v1",
1311-
"kind": "ControllerRevision",
1312-
"metadata": map[string]interface{}{
1313-
"name": "test-ss-7878b4b446",
1314-
"namespace": "default",
1315-
},
1316-
},
1317-
ownerReferences: []metav1.OwnerReference{
1318-
{
1319-
APIVersion: "apps/v1",
1320-
Kind: "StatefulSet",
1321-
Name: "test-ss",
1322-
UID: "fedcba",
1323-
},
1324-
},
1325-
enableWorkload: false,
1326-
want: false,
1327-
},
1328-
{
1329-
name: "standalone controllerrevision without owner should propagate",
1330-
obj: map[string]interface{}{
1331-
"apiVersion": "apps/v1",
1332-
"kind": "ControllerRevision",
1333-
"metadata": map[string]interface{}{
1334-
"name": "custom-revision",
1335-
"namespace": "default",
1336-
},
1337-
},
1338-
ownerReferences: nil,
1339-
enableWorkload: false,
1340-
want: true,
1341-
},
1342-
{
1343-
name: "PVC should propagate when workload is disabled",
1344-
obj: map[string]interface{}{
1345-
"apiVersion": "v1",
1346-
"kind": "PersistentVolumeClaim",
1347-
"metadata": map[string]interface{}{
1348-
"name": "test-pvc",
1349-
"namespace": "default",
1350-
},
1351-
},
1352-
ownerReferences: nil,
1353-
enableWorkload: false,
1354-
want: true,
1355-
},
1356-
{
1357-
name: "PVC should NOT propagate when workload is enabled",
1358-
obj: map[string]interface{}{
1359-
"apiVersion": "v1",
1360-
"kind": "PersistentVolumeClaim",
1361-
"metadata": map[string]interface{}{
1362-
"name": "test-pvc",
1363-
"namespace": "default",
1364-
},
1365-
},
1366-
ownerReferences: nil,
1367-
enableWorkload: true,
1368-
want: false,
1369-
},
1370-
{
1371-
name: "PVC with ownerReferences should NOT propagate when workload is enabled",
1372-
obj: map[string]interface{}{
1373-
"apiVersion": "v1",
1374-
"kind": "PersistentVolumeClaim",
1375-
"metadata": map[string]interface{}{
1376-
"name": "data-statefulset-0",
1377-
"namespace": "default",
1378-
},
1379-
},
1380-
ownerReferences: []metav1.OwnerReference{
1381-
{
1382-
APIVersion: "apps/v1",
1383-
Kind: "StatefulSet",
1384-
Name: "statefulset",
1385-
UID: "sts-uid",
1386-
},
1387-
},
1388-
enableWorkload: true,
1389-
want: false,
1390-
},
1391-
}
1392-
1393-
for _, tt := range tests {
1394-
t.Run(tt.name, func(t *testing.T) {
1395-
uObj := &unstructured.Unstructured{Object: tt.obj}
1396-
if tt.ownerReferences != nil {
1397-
uObj.SetOwnerReferences(tt.ownerReferences)
1398-
}
1399-
1400-
got, err := ShouldPropagateObj(nil, uObj, tt.enableWorkload)
1401-
if err != nil {
1402-
t.Errorf("ShouldPropagateObj() error = %v", err)
1403-
return
1404-
}
1405-
if got != tt.want {
1406-
t.Errorf("ShouldPropagateObj() = %v, want %v", got, tt.want)
1407-
}
1408-
})
1409-
}
1410-
}

0 commit comments

Comments
 (0)