@@ -1111,7 +1111,7 @@ func newTestClusterExtensionRevision(t *testing.T, revisionName string, ext *ocv
11111111 labels .ServiceAccountNamespaceKey : ext .Spec .Namespace ,
11121112 },
11131113 Labels : map [string ]string {
1114- labels .OwnerNameKey : "test- ext" ,
1114+ labels .OwnerNameKey : ext . Name ,
11151115 },
11161116 },
11171117 Spec : ocv1.ClusterExtensionRevisionSpec {
@@ -1344,6 +1344,258 @@ func (m *mockTrackingCache) Free(ctx context.Context, user client.Object) error
13441344 return nil
13451345}
13461346
1347+ func Test_ClusterExtensionRevisionReconciler_Reconcile_ForeignRevisionCollision (t * testing.T ) {
1348+ testScheme := newScheme (t )
1349+
1350+ for _ , tc := range []struct {
1351+ name string
1352+ reconcilingRevisionName string
1353+ existingObjs func () []client.Object
1354+ revisionResult machinery.RevisionResult
1355+ expectCollision bool
1356+ }{
1357+ {
1358+ name : "ActionProgressed with foreign controller ownerRef is treated as collision" ,
1359+ reconcilingRevisionName : "ext-B-1" ,
1360+ existingObjs : func () []client.Object {
1361+ extA := & ocv1.ClusterExtension {
1362+ ObjectMeta : metav1.ObjectMeta {Name : "ext-A" , UID : "ext-A-uid" },
1363+ Spec : ocv1.ClusterExtensionSpec {
1364+ Namespace : "ns-a" ,
1365+ ServiceAccount : ocv1.ServiceAccountReference {Name : "sa-a" },
1366+ Source : ocv1.SourceConfig {
1367+ SourceType : ocv1 .SourceTypeCatalog ,
1368+ Catalog : & ocv1.CatalogFilter {PackageName : "pkg" },
1369+ },
1370+ },
1371+ }
1372+ extB := & ocv1.ClusterExtension {
1373+ ObjectMeta : metav1.ObjectMeta {Name : "ext-B" , UID : "ext-B-uid" },
1374+ Spec : ocv1.ClusterExtensionSpec {
1375+ Namespace : "ns-b" ,
1376+ ServiceAccount : ocv1.ServiceAccountReference {Name : "sa-b" },
1377+ Source : ocv1.SourceConfig {
1378+ SourceType : ocv1 .SourceTypeCatalog ,
1379+ Catalog : & ocv1.CatalogFilter {PackageName : "pkg" },
1380+ },
1381+ },
1382+ }
1383+ // CER from ext-A's upgrade (revision 2) - this is the "foreign" owner
1384+ cerA2 := newTestClusterExtensionRevision (t , "ext-A-2" , extA , testScheme )
1385+
1386+ // CER from ext-B (revision 1) - the one being reconciled
1387+ cerB1 := newTestClusterExtensionRevision (t , "ext-B-1" , extB , testScheme )
1388+
1389+ return []client.Object {extA , extB , cerA2 , cerB1 }
1390+ },
1391+ revisionResult : mockRevisionResult {
1392+ phases : []machinery.PhaseResult {
1393+ mockPhaseResult {
1394+ name : "everything" ,
1395+ objects : []machinery.ObjectResult {
1396+ mockObjectResult {
1397+ action : machinery .ActionProgressed ,
1398+ object : func () machinery.Object {
1399+ obj := & unstructured.Unstructured {
1400+ Object : map [string ]interface {}{
1401+ "apiVersion" : "apiextensions.k8s.io/v1" ,
1402+ "kind" : "CustomResourceDefinition" ,
1403+ "metadata" : map [string ]interface {}{
1404+ "name" : "widgets.example.com" ,
1405+ "ownerReferences" : []interface {}{
1406+ map [string ]interface {}{
1407+ "apiVersion" : ocv1 .GroupVersion .String (),
1408+ "kind" : ocv1 .ClusterExtensionRevisionKind ,
1409+ "name" : "ext-A-2" ,
1410+ "uid" : "ext-A-2" ,
1411+ "controller" : true ,
1412+ "blockOwnerDeletion" : true ,
1413+ },
1414+ },
1415+ },
1416+ },
1417+ }
1418+ return obj
1419+ }(),
1420+ probes : machinerytypes.ProbeResultContainer {
1421+ boxcutter .ProgressProbeType : {
1422+ Status : machinerytypes .ProbeStatusTrue ,
1423+ },
1424+ },
1425+ },
1426+ },
1427+ },
1428+ },
1429+ },
1430+ expectCollision : true ,
1431+ },
1432+ {
1433+ name : "ActionProgressed with sibling controller ownerRef is NOT a collision" ,
1434+ reconcilingRevisionName : "ext-A-1" ,
1435+ existingObjs : func () []client.Object {
1436+ extA := & ocv1.ClusterExtension {
1437+ ObjectMeta : metav1.ObjectMeta {Name : "ext-A" , UID : "ext-A-uid" },
1438+ Spec : ocv1.ClusterExtensionSpec {
1439+ Namespace : "ns-a" ,
1440+ ServiceAccount : ocv1.ServiceAccountReference {Name : "sa-a" },
1441+ Source : ocv1.SourceConfig {
1442+ SourceType : ocv1 .SourceTypeCatalog ,
1443+ Catalog : & ocv1.CatalogFilter {PackageName : "pkg" },
1444+ },
1445+ },
1446+ }
1447+ // CER from ext-A revision 1 (being reconciled, older)
1448+ cerA1 := newTestClusterExtensionRevision (t , "ext-A-1" , extA , testScheme )
1449+ // CER from ext-A revision 2 (newer, already progressed)
1450+ cerA2 := newTestClusterExtensionRevision (t , "ext-A-2" , extA , testScheme )
1451+
1452+ return []client.Object {extA , cerA1 , cerA2 }
1453+ },
1454+ revisionResult : mockRevisionResult {
1455+ phases : []machinery.PhaseResult {
1456+ mockPhaseResult {
1457+ name : "everything" ,
1458+ objects : []machinery.ObjectResult {
1459+ mockObjectResult {
1460+ action : machinery .ActionProgressed ,
1461+ object : func () machinery.Object {
1462+ obj := & unstructured.Unstructured {
1463+ Object : map [string ]interface {}{
1464+ "apiVersion" : "apiextensions.k8s.io/v1" ,
1465+ "kind" : "CustomResourceDefinition" ,
1466+ "metadata" : map [string ]interface {}{
1467+ "name" : "widgets.example.com" ,
1468+ "ownerReferences" : []interface {}{
1469+ map [string ]interface {}{
1470+ "apiVersion" : ocv1 .GroupVersion .String (),
1471+ "kind" : ocv1 .ClusterExtensionRevisionKind ,
1472+ "name" : "ext-A-2" ,
1473+ "uid" : "ext-A-2" ,
1474+ "controller" : true ,
1475+ "blockOwnerDeletion" : true ,
1476+ },
1477+ },
1478+ },
1479+ },
1480+ }
1481+ return obj
1482+ }(),
1483+ probes : machinerytypes.ProbeResultContainer {
1484+ boxcutter .ProgressProbeType : {
1485+ Status : machinerytypes .ProbeStatusTrue ,
1486+ },
1487+ },
1488+ },
1489+ },
1490+ },
1491+ },
1492+ },
1493+ expectCollision : false ,
1494+ },
1495+ {
1496+ name : "ActionProgressed with non-CER controller ownerRef is NOT a collision" ,
1497+ reconcilingRevisionName : "ext-B-1" ,
1498+ existingObjs : func () []client.Object {
1499+ extB := & ocv1.ClusterExtension {
1500+ ObjectMeta : metav1.ObjectMeta {Name : "ext-B" , UID : "ext-B-uid" },
1501+ Spec : ocv1.ClusterExtensionSpec {
1502+ Namespace : "ns-b" ,
1503+ ServiceAccount : ocv1.ServiceAccountReference {Name : "sa-b" },
1504+ Source : ocv1.SourceConfig {
1505+ SourceType : ocv1 .SourceTypeCatalog ,
1506+ Catalog : & ocv1.CatalogFilter {PackageName : "pkg" },
1507+ },
1508+ },
1509+ }
1510+ cerB1 := newTestClusterExtensionRevision (t , "ext-B-1" , extB , testScheme )
1511+
1512+ return []client.Object {extB , cerB1 }
1513+ },
1514+ revisionResult : mockRevisionResult {
1515+ phases : []machinery.PhaseResult {
1516+ mockPhaseResult {
1517+ name : "everything" ,
1518+ objects : []machinery.ObjectResult {
1519+ mockObjectResult {
1520+ action : machinery .ActionProgressed ,
1521+ object : func () machinery.Object {
1522+ obj := & unstructured.Unstructured {
1523+ Object : map [string ]interface {}{
1524+ "apiVersion" : "v1" ,
1525+ "kind" : "ConfigMap" ,
1526+ "metadata" : map [string ]interface {}{
1527+ "name" : "some-cm" ,
1528+ "namespace" : "default" ,
1529+ "ownerReferences" : []interface {}{
1530+ map [string ]interface {}{
1531+ "apiVersion" : "apps/v1" ,
1532+ "kind" : "Deployment" ,
1533+ "name" : "some-deployment" ,
1534+ "uid" : "deploy-uid" ,
1535+ "controller" : true ,
1536+ "blockOwnerDeletion" : true ,
1537+ },
1538+ },
1539+ },
1540+ },
1541+ }
1542+ return obj
1543+ }(),
1544+ probes : machinerytypes.ProbeResultContainer {
1545+ boxcutter .ProgressProbeType : {
1546+ Status : machinerytypes .ProbeStatusTrue ,
1547+ },
1548+ },
1549+ },
1550+ },
1551+ },
1552+ },
1553+ },
1554+ expectCollision : false ,
1555+ },
1556+ } {
1557+ t .Run (tc .name , func (t * testing.T ) {
1558+ testClient := fake .NewClientBuilder ().
1559+ WithScheme (testScheme ).
1560+ WithStatusSubresource (& ocv1.ClusterExtensionRevision {}).
1561+ WithObjects (tc .existingObjs ()... ).
1562+ Build ()
1563+
1564+ mockEngine := & mockRevisionEngine {
1565+ reconcile : func (ctx context.Context , rev machinerytypes.Revision , opts ... machinerytypes.RevisionReconcileOption ) (machinery.RevisionResult , error ) {
1566+ return tc .revisionResult , nil
1567+ },
1568+ }
1569+ result , err := (& controllers.ClusterExtensionRevisionReconciler {
1570+ Client : testClient ,
1571+ RevisionEngineFactory : & mockRevisionEngineFactory {engine : mockEngine },
1572+ TrackingCache : & mockTrackingCache {client : testClient },
1573+ }).Reconcile (t .Context (), ctrl.Request {
1574+ NamespacedName : types.NamespacedName {
1575+ Name : tc .reconcilingRevisionName ,
1576+ },
1577+ })
1578+
1579+ if tc .expectCollision {
1580+ require .Equal (t , ctrl.Result {RequeueAfter : 10 * time .Second }, result )
1581+ require .NoError (t , err )
1582+
1583+ rev := & ocv1.ClusterExtensionRevision {}
1584+ require .NoError (t , testClient .Get (t .Context (), client.ObjectKey {Name : tc .reconcilingRevisionName }, rev ))
1585+ cond := meta .FindStatusCondition (rev .Status .Conditions , ocv1 .ClusterExtensionRevisionTypeProgressing )
1586+ require .NotNil (t , cond )
1587+ require .Equal (t , metav1 .ConditionTrue , cond .Status )
1588+ require .Equal (t , ocv1 .ClusterExtensionRevisionReasonRetrying , cond .Reason )
1589+ require .Contains (t , cond .Message , "revision object collisions" )
1590+ require .Contains (t , cond .Message , "Conflicting Owner" )
1591+ } else {
1592+ require .Equal (t , ctrl.Result {}, result )
1593+ require .NoError (t , err )
1594+ }
1595+ })
1596+ }
1597+ }
1598+
13471599func Test_effectiveCollisionProtection (t * testing.T ) {
13481600 for _ , tc := range []struct {
13491601 name string
0 commit comments