@@ -1318,6 +1318,217 @@ func TestSandboxClaimCreationMetric(t *testing.T) {
13181318 })
13191319}
13201320
1321+ func TestSandboxClaimAdoptionSkipsStaleContentHash (t * testing.T ) {
1322+ scheme := newScheme (t )
1323+
1324+ template := & extensionsv1alpha1.SandboxTemplate {
1325+ ObjectMeta : metav1.ObjectMeta {Name : "test-template" , Namespace : "default" },
1326+ Spec : extensionsv1alpha1.SandboxTemplateSpec {
1327+ PodTemplate : sandboxv1alpha1.PodTemplate {
1328+ Spec : corev1.PodSpec {
1329+ Containers : []corev1.Container {{Name : "c" , Image : "new-image:v2" }},
1330+ },
1331+ },
1332+ },
1333+ }
1334+
1335+ claim := & extensionsv1alpha1.SandboxClaim {
1336+ ObjectMeta : metav1.ObjectMeta {
1337+ Name : "hash-claim" , Namespace : "default" ,
1338+ UID : "hash-claim-uid" ,
1339+ },
1340+ Spec : extensionsv1alpha1.SandboxClaimSpec {
1341+ TemplateRef : extensionsv1alpha1.SandboxTemplateRef {Name : "test-template" },
1342+ },
1343+ }
1344+
1345+ poolNameHash := sandboxcontrollers .NameHash ("test-pool" )
1346+ currentHash := templateContentHash (template )
1347+
1348+ // Stale sandbox: content hash from old template version.
1349+ staleSandbox := & sandboxv1alpha1.Sandbox {
1350+ ObjectMeta : metav1.ObjectMeta {
1351+ Name : "stale-sb" , Namespace : "default" ,
1352+ CreationTimestamp : metav1.Time {Time : time .Now ().Add (- 1 * time .Hour )},
1353+ Labels : map [string ]string {
1354+ warmPoolSandboxLabel : poolNameHash ,
1355+ sandboxTemplateRefHash : sandboxcontrollers .NameHash ("test-template" ),
1356+ templateContentHashLabel : "oldold00" ,
1357+ },
1358+ OwnerReferences : []metav1.OwnerReference {{
1359+ APIVersion : "extensions.agents.x-k8s.io/v1alpha1" , Kind : "SandboxWarmPool" ,
1360+ Name : "test-pool" , UID : "wp-uid" , Controller : ptr .To (true ),
1361+ }},
1362+ },
1363+ Spec : sandboxv1alpha1.SandboxSpec {
1364+ Replicas : ptr .To (int32 (1 )),
1365+ PodTemplate : sandboxv1alpha1.PodTemplate {
1366+ Spec : corev1.PodSpec {Containers : []corev1.Container {{Name : "c" , Image : "old-image:v1" }}},
1367+ },
1368+ },
1369+ Status : sandboxv1alpha1.SandboxStatus {
1370+ Conditions : []metav1.Condition {{
1371+ Type : string (sandboxv1alpha1 .SandboxConditionReady ), Status : metav1 .ConditionTrue , Reason : "Ready" ,
1372+ }},
1373+ },
1374+ }
1375+
1376+ // Fresh sandbox: content hash matches current template.
1377+ freshSandbox := & sandboxv1alpha1.Sandbox {
1378+ ObjectMeta : metav1.ObjectMeta {
1379+ Name : "fresh-sb" , Namespace : "default" ,
1380+ CreationTimestamp : metav1 .Now (),
1381+ Labels : map [string ]string {
1382+ warmPoolSandboxLabel : poolNameHash ,
1383+ sandboxTemplateRefHash : sandboxcontrollers .NameHash ("test-template" ),
1384+ templateContentHashLabel : currentHash ,
1385+ },
1386+ OwnerReferences : []metav1.OwnerReference {{
1387+ APIVersion : "extensions.agents.x-k8s.io/v1alpha1" , Kind : "SandboxWarmPool" ,
1388+ Name : "test-pool" , UID : "wp-uid" , Controller : ptr .To (true ),
1389+ }},
1390+ },
1391+ Spec : sandboxv1alpha1.SandboxSpec {
1392+ Replicas : ptr .To (int32 (1 )),
1393+ PodTemplate : sandboxv1alpha1.PodTemplate {
1394+ Spec : corev1.PodSpec {Containers : []corev1.Container {{Name : "c" , Image : "new-image:v2" }}},
1395+ },
1396+ },
1397+ Status : sandboxv1alpha1.SandboxStatus {
1398+ Conditions : []metav1.Condition {{
1399+ Type : string (sandboxv1alpha1 .SandboxConditionReady ), Status : metav1 .ConditionTrue , Reason : "Ready" ,
1400+ }},
1401+ },
1402+ }
1403+
1404+ reconciler := & SandboxClaimReconciler {
1405+ Client : fake .NewClientBuilder ().
1406+ WithScheme (scheme ).
1407+ WithObjects (template , claim , staleSandbox , freshSandbox ).
1408+ WithStatusSubresource (claim ).
1409+ Build (),
1410+ Scheme : scheme ,
1411+ Recorder : record .NewFakeRecorder (10 ),
1412+ Tracer : asmetrics .NewNoOp (),
1413+ }
1414+
1415+ req := reconcile.Request {NamespacedName : types.NamespacedName {Name : "hash-claim" , Namespace : "default" }}
1416+ _ , err := reconciler .Reconcile (context .Background (), req )
1417+ if err != nil {
1418+ t .Fatalf ("reconcile failed: %v" , err )
1419+ }
1420+
1421+ // Should have adopted the fresh sandbox, not the stale one.
1422+ var adopted sandboxv1alpha1.Sandbox
1423+ err = reconciler .Get (context .Background (), types.NamespacedName {Name : "fresh-sb" , Namespace : "default" }, & adopted )
1424+ if err != nil {
1425+ t .Fatalf ("failed to get fresh sandbox: %v" , err )
1426+ }
1427+ controllerRef := metav1 .GetControllerOf (& adopted )
1428+ if controllerRef == nil || controllerRef .UID != claim .UID {
1429+ t .Fatalf ("expected fresh-sb to be adopted by claim, got controller %v" , controllerRef )
1430+ }
1431+
1432+ // Stale sandbox should still be owned by warm pool (not adopted).
1433+ var stale sandboxv1alpha1.Sandbox
1434+ err = reconciler .Get (context .Background (), types.NamespacedName {Name : "stale-sb" , Namespace : "default" }, & stale )
1435+ if err != nil {
1436+ t .Fatalf ("failed to get stale sandbox: %v" , err )
1437+ }
1438+ staleRef := metav1 .GetControllerOf (& stale )
1439+ if staleRef != nil && staleRef .UID == claim .UID {
1440+ t .Fatal ("stale sandbox should not have been adopted by claim" )
1441+ }
1442+ }
1443+
1444+ func TestSandboxClaimAdoptionRemovesContentHashLabel (t * testing.T ) {
1445+ scheme := newScheme (t )
1446+
1447+ template := & extensionsv1alpha1.SandboxTemplate {
1448+ ObjectMeta : metav1.ObjectMeta {Name : "test-template" , Namespace : "default" },
1449+ Spec : extensionsv1alpha1.SandboxTemplateSpec {
1450+ PodTemplate : sandboxv1alpha1.PodTemplate {
1451+ Spec : corev1.PodSpec {
1452+ Containers : []corev1.Container {{Name : "c" , Image : "img" }},
1453+ },
1454+ },
1455+ },
1456+ }
1457+
1458+ claim := & extensionsv1alpha1.SandboxClaim {
1459+ ObjectMeta : metav1.ObjectMeta {
1460+ Name : "label-claim" , Namespace : "default" , UID : "label-claim-uid" ,
1461+ },
1462+ Spec : extensionsv1alpha1.SandboxClaimSpec {
1463+ TemplateRef : extensionsv1alpha1.SandboxTemplateRef {Name : "test-template" },
1464+ },
1465+ }
1466+
1467+ poolNameHash := sandboxcontrollers .NameHash ("test-pool" )
1468+ contentHash := templateContentHash (template )
1469+
1470+ poolSandbox := & sandboxv1alpha1.Sandbox {
1471+ ObjectMeta : metav1.ObjectMeta {
1472+ Name : "pool-sb" , Namespace : "default" ,
1473+ CreationTimestamp : metav1 .Now (),
1474+ Labels : map [string ]string {
1475+ warmPoolSandboxLabel : poolNameHash ,
1476+ sandboxTemplateRefHash : sandboxcontrollers .NameHash ("test-template" ),
1477+ templateContentHashLabel : contentHash ,
1478+ },
1479+ OwnerReferences : []metav1.OwnerReference {{
1480+ APIVersion : "extensions.agents.x-k8s.io/v1alpha1" , Kind : "SandboxWarmPool" ,
1481+ Name : "test-pool" , UID : "wp-uid" , Controller : ptr .To (true ),
1482+ }},
1483+ },
1484+ Spec : sandboxv1alpha1.SandboxSpec {
1485+ Replicas : ptr .To (int32 (1 )),
1486+ PodTemplate : sandboxv1alpha1.PodTemplate {
1487+ Spec : corev1.PodSpec {Containers : []corev1.Container {{Name : "c" , Image : "img" }}},
1488+ },
1489+ },
1490+ Status : sandboxv1alpha1.SandboxStatus {
1491+ Conditions : []metav1.Condition {{
1492+ Type : string (sandboxv1alpha1 .SandboxConditionReady ), Status : metav1 .ConditionTrue , Reason : "Ready" ,
1493+ }},
1494+ },
1495+ }
1496+
1497+ reconciler := & SandboxClaimReconciler {
1498+ Client : fake .NewClientBuilder ().
1499+ WithScheme (scheme ).
1500+ WithObjects (template , claim , poolSandbox ).
1501+ WithStatusSubresource (claim ).
1502+ Build (),
1503+ Scheme : scheme ,
1504+ Recorder : record .NewFakeRecorder (10 ),
1505+ Tracer : asmetrics .NewNoOp (),
1506+ }
1507+
1508+ req := reconcile.Request {NamespacedName : types.NamespacedName {Name : "label-claim" , Namespace : "default" }}
1509+ _ , err := reconciler .Reconcile (context .Background (), req )
1510+ if err != nil {
1511+ t .Fatalf ("reconcile failed: %v" , err )
1512+ }
1513+
1514+ var adopted sandboxv1alpha1.Sandbox
1515+ err = reconciler .Get (context .Background (), types.NamespacedName {Name : "pool-sb" , Namespace : "default" }, & adopted )
1516+ if err != nil {
1517+ t .Fatalf ("failed to get adopted sandbox: %v" , err )
1518+ }
1519+
1520+ // All warm pool labels should be removed after adoption.
1521+ if _ , exists := adopted .Labels [warmPoolSandboxLabel ]; exists {
1522+ t .Error ("warmPoolSandboxLabel should be removed after adoption" )
1523+ }
1524+ if _ , exists := adopted .Labels [sandboxTemplateRefHash ]; exists {
1525+ t .Error ("sandboxTemplateRefHash should be removed after adoption" )
1526+ }
1527+ if _ , exists := adopted .Labels [templateContentHashLabel ]; exists {
1528+ t .Error ("templateContentHashLabel should be removed after adoption" )
1529+ }
1530+ }
1531+
13211532func newScheme (t * testing.T ) * runtime.Scheme {
13221533 scheme := runtime .NewScheme ()
13231534 if err := sandboxv1alpha1 .AddToScheme (scheme ); err != nil {
0 commit comments