@@ -285,10 +285,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati
285285 defer nodeList .PrintDebugCommand () // for debugging purpose in case of failed deployment
286286
287287 exutil .By ("Remove the image from all nodes in the pool" )
288- for _ , node := range allNodes {
289- // We ignore errors, since the image can be present or not in the nodes
290- _ = NewRemoteImage (node , pinnedImage ).Rmi ()
291- }
288+ removeImageFromNodes (allNodes , pinnedImage )
292289 logger .Infof ("OK!\n " )
293290
294291 exutil .By ("Create pinnedimageset" )
@@ -516,6 +513,91 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati
516513 basicPinnedImageTest (infraMcp , pinnedImageSetName )
517514
518515 })
516+
517+ g .It ("[PolarionID:88378][OTP] Deleting a PinnedImageSet does not affect images pinned by another PinnedImageSet" , func () {
518+ var (
519+ waitForPinned = 10 * time .Minute
520+ pinnedImage = AlpineImage
521+ allNodes = mcp .GetNodesOrFail ()
522+ pisOneName = fmt .Sprintf ("tc-%s-pis-one" , GetCurrentTestPolarionIDNumber ())
523+ pisTwoName = fmt .Sprintf ("tc-%s-pis-two" , GetCurrentTestPolarionIDNumber ())
524+ pisDupName = fmt .Sprintf ("tc-%s-pis-duplicate" , GetCurrentTestPolarionIDNumber ())
525+ )
526+
527+ exutil .By ("Remove the test image from all nodes in the pool" )
528+ removeImageFromNodes (allNodes , pinnedImage )
529+ logger .Infof ("OK!\n " )
530+
531+ exutil .By ("Create first PinnedImageSet with alpine image and verify it is pinned" )
532+ pisOne := createPinnedImageSetAndWait (oc .AsAdmin (), pisOneName , mcp , []string {pinnedImage }, allNodes , waitForPinned )
533+ defer pisOne .DeleteAndWait (waitForPinned )
534+ logger .Infof ("OK!\n " )
535+
536+ exutil .By ("Create second PinnedImageSet with the same alpine image and verify it is pinned" )
537+ pisTwo := createPinnedImageSetAndWait (oc .AsAdmin (), pisTwoName , mcp , []string {pinnedImage }, allNodes , waitForPinned )
538+ defer pisTwo .DeleteAndWait (waitForPinned )
539+ logger .Infof ("OK!\n " )
540+
541+ exutil .By ("Verify all MachineConfigNodes report healthy pinned image conditions" )
542+ for _ , node := range allNodes {
543+ mcn := node .GetMachineConfigNode ()
544+ o .Eventually (mcn , "2m" , "20s" ).Should (HaveConditionField ("PinnedImageSetsDegraded" , "status" , FalseString ),
545+ "MachineConfigNode %s should not be PinnedImageSetsDegraded.\n %s" , node .GetName (), mcn .PrettyString ())
546+ o .Eventually (mcn , "2m" , "20s" ).Should (HaveConditionField ("PinnedImageSetsProgressing" , "status" , FalseString ),
547+ "MachineConfigNode %s should not be PinnedImageSetsProgressing.\n %s" , node .GetName (), mcn .PrettyString ())
548+ }
549+ logger .Infof ("OK!\n " )
550+
551+ exutil .By ("Delete the first PinnedImageSet" )
552+ o .Expect (pisOne .Delete ()).To (o .Succeed (), "Error deleting %s" , pisOne )
553+ logger .Infof ("OK!\n " )
554+
555+ exutil .By ("Wait for the pool to reconcile after deleting the first PinnedImageSet" )
556+ o .Expect (mcp .waitForPinComplete (waitForPinned )).To (o .Succeed (),
557+ "Pinned image operation is not completed in %s after deleting %s" , mcp , pisOne )
558+ logger .Infof ("OK!\n " )
559+
560+ exutil .By ("Verify the first PinnedImageSet is deleted and the second still exists" )
561+ o .Expect (pisOne .Exists ()).To (o .BeFalse (),
562+ "%s should not exist after deletion" , pisOne )
563+ o .Expect (pisTwo .Exists ()).To (o .BeTrue (),
564+ "%s should still exist" , pisTwo )
565+ logger .Infof ("OK!\n " )
566+
567+ exutil .By ("Verify the image is STILL pinned on all nodes after deleting the first PinnedImageSet" )
568+ for _ , node := range allNodes {
569+ ri := NewRemoteImage (node , pinnedImage )
570+ o .Expect (ri .IsPinned ()).To (o .BeTrue (),
571+ "%s should still be pinned because %s still references it" , ri , pisTwo )
572+ }
573+ logger .Infof ("OK!\n " )
574+
575+ exutil .By ("Verify MachineConfigNodes remain healthy after the deletion" )
576+ for _ , node := range allNodes {
577+ mcn := node .GetMachineConfigNode ()
578+ o .Eventually (mcn , "2m" , "20s" ).Should (HaveConditionField ("PinnedImageSetsDegraded" , "status" , FalseString ),
579+ "MachineConfigNode %s should not be PinnedImageSetsDegraded after deleting %s.\n %s" , node .GetName (), pisOne , mcn .PrettyString ())
580+ o .Eventually (mcn , "2m" , "20s" ).Should (HaveConditionField ("PinnedImageSetsProgressing" , "status" , FalseString ),
581+ "MachineConfigNode %s should not be PinnedImageSetsProgressing after deleting %s.\n %s" , node .GetName (), pisOne , mcn .PrettyString ())
582+ }
583+ logger .Infof ("OK!\n " )
584+
585+ exutil .By ("Verify that a PinnedImageSet with duplicate images is rejected by the API" )
586+ _ , err := CreateGenericPinnedImageSet (oc .AsAdmin (), pisDupName , mcp .GetName (), []string {pinnedImage , pinnedImage })
587+ o .Expect (err ).To (o .HaveOccurred (),
588+ "Creating a PinnedImageSet with duplicate images should fail, but it succeeded" )
589+ o .Expect (err ).To (o .BeAssignableToTypeOf (& exutil.ExitError {}),
590+ "Expected an ExitError from the API server rejection" )
591+ o .Expect (err .(* exutil.ExitError ).StdErr ).To (o .ContainSubstring ("Duplicate value" ),
592+ "API error should indicate duplicate image entries" )
593+ logger .Infof ("OK!\n " )
594+
595+ exutil .By ("Verify the duplicate PinnedImageSet was not created" )
596+ pisDup := NewPinnedImageSet (oc .AsAdmin (), pisDupName )
597+ o .Expect (pisDup .Exists ()).To (o .BeFalse (),
598+ "%s should not exist because the API rejected it" , pisDup )
599+ logger .Infof ("OK!\n " )
600+ })
519601})
520602
521603// getReleaseInfoPullspecOrFail returns a list of strings containing the names of the pullspec images
@@ -601,10 +683,7 @@ func DigestMirrorTest(oc *exutil.CLI, mcp *MachineConfigPool, idmsName, idmsMirr
601683 )
602684
603685 exutil .By ("Remove the image from all nodes in the pool" )
604- for _ , node := range allNodes {
605- // We ignore errors, since the image can be present or not in the nodes
606- _ = NewRemoteImage (node , pinnedImage ).Rmi ()
607- }
686+ removeImageFromNodes (allNodes , pinnedImage )
608687 logger .Infof ("OK!\n " )
609688
610689 exutil .By ("Create new machine config to deploy a ImageDigestMirrorSet configuring a mirror registry" )
@@ -696,3 +775,30 @@ func basicPinnedImageTest(mcp *MachineConfigPool, pinnedImageSetName string) {
696775 o .Expect (secondPinnedImage .IsPinned ()).To (o .BeFalse (), "%s is pinned, but it should NOT" , secondPinnedImage )
697776 logger .Infof ("OK!\n " )
698777}
778+
779+ // removeImageFromNodes removes the given image from all provided nodes, ignoring errors since the image may not be present
780+ func removeImageFromNodes (nodes []* Node , image string ) {
781+ for _ , node := range nodes {
782+ _ = NewRemoteImage (node , image ).Rmi ()
783+ }
784+ }
785+
786+ // createPinnedImageSetAndWait creates a PinnedImageSet, waits for the pool to complete pinning,
787+ // and verifies the images are pinned on all provided nodes
788+ func createPinnedImageSetAndWait (oc * exutil.CLI , name string , mcp * MachineConfigPool , images []string , nodes []* Node , waitForPinned time.Duration ) * PinnedImageSet {
789+ pis , err := CreateGenericPinnedImageSet (oc , name , mcp .GetName (), images )
790+ o .Expect (err ).NotTo (o .HaveOccurred (), "Error creating pinnedimageset %s" , pis )
791+
792+ o .Expect (mcp .waitForPinComplete (waitForPinned )).To (o .Succeed (),
793+ "Pinned image operation is not completed in %s" , mcp )
794+
795+ for _ , node := range nodes {
796+ for _ , image := range images {
797+ ri := NewRemoteImage (node , image )
798+ o .Expect (ri .IsPinned ()).To (o .BeTrue (),
799+ "%s is not pinned, but it should be" , ri )
800+ }
801+ }
802+
803+ return pis
804+ }
0 commit comments