@@ -24,6 +24,7 @@ import (
24
24
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25
25
"k8s.io/apimachinery/pkg/runtime"
26
26
"k8s.io/apimachinery/pkg/runtime/schema"
27
+ "k8s.io/apimachinery/pkg/types"
27
28
"k8s.io/apimachinery/pkg/watch"
28
29
"k8s.io/client-go/dynamic/fake"
29
30
"k8s.io/client-go/kubernetes/scheme"
@@ -1157,6 +1158,102 @@ func TestIterateHierachyV2(t *testing.T) {
1157
1158
})
1158
1159
}
1159
1160
1161
+ // TestIterateHierarchyV2_CrossNamespaceOwnerReference tests that cross-namespace
1162
+ // owner references work correctly, specifically cluster-scoped resources with
1163
+ // namespaced children
1164
+ func TestIterateHierarchyV2_CrossNamespaceOwnerReference (t * testing.T ) {
1165
+ cluster := newCluster (t )
1166
+
1167
+ // Create cluster-scoped parent resource (ProviderRevision)
1168
+ parentUID := types .UID ("parent-uid-123" )
1169
+ clusterScopedParent := & Resource {
1170
+ Ref : corev1.ObjectReference {
1171
+ APIVersion : "pkg.crossplane.io/v1" ,
1172
+ Kind : "ProviderRevision" ,
1173
+ Name : "provider-aws-cloudformation-3b2c213545b8" ,
1174
+ UID : parentUID ,
1175
+ // No namespace = cluster-scoped
1176
+ },
1177
+ OwnerRefs : []metav1.OwnerReference {},
1178
+ }
1179
+
1180
+ // Create namespaced child with ownerReference to cluster-scoped parent
1181
+ namespacedChild := & Resource {
1182
+ Ref : corev1.ObjectReference {
1183
+ APIVersion : "apps/v1" ,
1184
+ Kind : "Deployment" ,
1185
+ Name : "provider-aws-cloudformation-3b2c213545b8" ,
1186
+ Namespace : "crossplane-system" ,
1187
+ UID : types .UID ("child-uid-456" ),
1188
+ },
1189
+ OwnerRefs : []metav1.OwnerReference {{
1190
+ APIVersion : "pkg.crossplane.io/v1" ,
1191
+ Kind : "ProviderRevision" ,
1192
+ Name : "provider-aws-cloudformation-3b2c213545b8" ,
1193
+ // UID: parentUID, // Don't set UID - let it be resolved via cross-namespace lookup
1194
+ }},
1195
+ }
1196
+
1197
+ // Create cluster-scoped child with ownerReference to cluster-scoped parent (this should work already)
1198
+ clusterScopedChild := & Resource {
1199
+ Ref : corev1.ObjectReference {
1200
+ APIVersion : "rbac.authorization.k8s.io/v1" ,
1201
+ Kind : "ClusterRole" ,
1202
+ Name : "crossplane:provider:provider-aws-cloudformation-3b2c213545b8:system" ,
1203
+ UID : types .UID ("child-uid-789" ),
1204
+ // No namespace = cluster-scoped
1205
+ },
1206
+ OwnerRefs : []metav1.OwnerReference {{
1207
+ APIVersion : "pkg.crossplane.io/v1" ,
1208
+ Kind : "ProviderRevision" ,
1209
+ Name : "provider-aws-cloudformation-3b2c213545b8" ,
1210
+ UID : parentUID ,
1211
+ }},
1212
+ }
1213
+
1214
+ // Add all resources to cluster cache
1215
+ cluster .setNode (clusterScopedParent )
1216
+ cluster .setNode (namespacedChild )
1217
+ cluster .setNode (clusterScopedChild )
1218
+
1219
+ // Test hierarchy traversal starting from cluster-scoped parent
1220
+ var visitedResources []* Resource
1221
+ cluster .IterateHierarchyV2 (
1222
+ []kube.ResourceKey {clusterScopedParent .ResourceKey ()},
1223
+ func (resource * Resource , namespaceResources map [kube.ResourceKey ]* Resource ) bool {
1224
+ visitedResources = append (visitedResources , resource )
1225
+ return true
1226
+ },
1227
+ )
1228
+
1229
+ // Should visit parent + both children (3 resources)
1230
+ assert .Equal (t , 3 , len (visitedResources ), "Should visit parent and both children" )
1231
+
1232
+ visitedNames := make ([]string , len (visitedResources ))
1233
+ for i , res := range visitedResources {
1234
+ visitedNames [i ] = res .Ref .Name
1235
+ }
1236
+
1237
+ // Check we have the expected resources by type and namespace combination
1238
+ foundParent := false
1239
+ foundClusterChild := false
1240
+ foundNamespacedChild := false
1241
+
1242
+ for _ , res := range visitedResources {
1243
+ if res .Ref .Kind == "ProviderRevision" && res .Ref .Namespace == "" {
1244
+ foundParent = true
1245
+ } else if res .Ref .Kind == "ClusterRole" && res .Ref .Namespace == "" {
1246
+ foundClusterChild = true
1247
+ } else if res .Ref .Kind == "Deployment" && res .Ref .Namespace == "crossplane-system" {
1248
+ foundNamespacedChild = true
1249
+ }
1250
+ }
1251
+
1252
+ assert .True (t , foundParent , "Should visit ProviderRevision parent" )
1253
+ assert .True (t , foundClusterChild , "Should visit ClusterRole child" )
1254
+ assert .True (t , foundNamespacedChild , "Should visit Deployment child (this tests the fix)" )
1255
+ }
1256
+
1160
1257
// Test_watchEvents_Deadlock validates that starting watches will not create a deadlock
1161
1258
// caused by using improper locking in various callback methods when there is a high load on the
1162
1259
// system.
@@ -1273,7 +1370,7 @@ func BenchmarkBuildGraph(b *testing.B) {
1273
1370
testResources := buildTestResourceMap ()
1274
1371
b .ResetTimer ()
1275
1372
for n := 0 ; n < b .N ; n ++ {
1276
- buildGraph (testResources )
1373
+ buildGraph (testResources , nil )
1277
1374
}
1278
1375
}
1279
1376
0 commit comments