2020import com .cronutils .utils .StringUtils ;
2121import com .fasterxml .jackson .databind .JsonNode ;
2222import com .google .common .collect .ImmutableMap ;
23+ import com .yugabyte .yw .common .config .GlobalConfKeys ;
2324import com .yugabyte .yw .common .config .RuntimeConfGetter ;
2425import com .yugabyte .yw .common .utils .FileUtils ;
2526import com .yugabyte .yw .forms .UniverseDefinitionTaskParams ;
2627import com .yugabyte .yw .forms .UniverseDefinitionTaskParams .Cluster ;
2728import com .yugabyte .yw .forms .UniverseDefinitionTaskParams .ClusterType ;
2829import com .yugabyte .yw .forms .UniverseDefinitionTaskParams .UserIntent ;
30+ import com .yugabyte .yw .models .AttachDetachSpec ;
2931import com .yugabyte .yw .models .Customer ;
3032import com .yugabyte .yw .models .Universe ;
3133import com .yugabyte .yw .models .Users ;
@@ -607,8 +609,9 @@ public void testIsUniverseOwner_NullStoredYwUuid() {
607609 when (consistencyInfo .getYwUUID ()).thenReturn (null );
608610 when (ysqlQueryExecutor .getConsistencyInfo (any (Universe .class ))).thenReturn (consistencyInfo );
609611
610- assertFalse (
611- "Should return false when stored YW UUID is null (not matching current UUID)" ,
612+ // When stored UUID is null (YCQL-only universe), assume owned and return true
613+ assertTrue (
614+ "Should return true when stored YW UUID is null (YCQL-only universe, skip validation)" ,
612615 Util .isUniverseOwner (universe , configHelper , ysqlQueryExecutor , confGetter ));
613616 }
614617
@@ -638,15 +641,9 @@ public void testGetStoredYwUuid_NullConsistencyInfo() {
638641 when (universe .getName ()).thenReturn ("test-universe" );
639642 when (ysqlQueryExecutor .getConsistencyInfo (any (Universe .class ))).thenReturn (null );
640643
641- PlatformServiceException exception =
642- assertThrows (
643- "Should throw PlatformServiceException when consistency info is null" ,
644- PlatformServiceException .class ,
645- () -> Util .getStoredYwUuid (universe , ysqlQueryExecutor , confGetter ));
646-
647- assertTrue (
648- "Exception should mention error querying YW UUID" ,
649- exception .getMessage ().contains ("Error querying YW UUID" ));
644+ // When consistency info is null (e.g., YCQL-only universe), should return null gracefully
645+ UUID result = Util .getStoredYwUuid (universe , ysqlQueryExecutor , confGetter );
646+ assertNull ("Should return null when consistency info is null (YCQL-only universe)" , result );
650647 }
651648
652649 @ Test
@@ -677,8 +674,18 @@ public void testvalidateUniverseOwnershipAndNotDetached_DetachedUniverse() {
677674 YsqlQueryExecutor ysqlQueryExecutor = mock (YsqlQueryExecutor .class );
678675 RuntimeConfGetter confGetter = mock (RuntimeConfGetter .class );
679676
677+ // Mock attach/detach feature enabled
678+ when (confGetter .getGlobalConf (GlobalConfKeys .attachDetachEnabled )).thenReturn (true );
679+
680680 UniverseDefinitionTaskParams taskParams = new UniverseDefinitionTaskParams ();
681681 taskParams .universeDetached = true ;
682+
683+ // Set up primary cluster with valid version to ensure validation runs
684+ UniverseDefinitionTaskParams .UserIntent userIntent =
685+ new UniverseDefinitionTaskParams .UserIntent ();
686+ userIntent .ybSoftwareVersion = "2024.2.0.0-b1" ; // Valid version for attach/detach
687+ taskParams .upsertPrimaryCluster (userIntent , null );
688+
682689 when (universe .getUniverseDetails ()).thenReturn (taskParams );
683690 when (universe .getName ()).thenReturn ("test-universe" );
684691
@@ -702,9 +709,20 @@ public void testvalidateUniverseOwnershipAndNotDetached_NotOwner() {
702709 YsqlQueryExecutor ysqlQueryExecutor = mock (YsqlQueryExecutor .class );
703710 RuntimeConfGetter confGetter = mock (RuntimeConfGetter .class );
704711
712+ // Mock attach/detach feature enabled
713+ when (confGetter .getGlobalConf (GlobalConfKeys .attachDetachEnabled )).thenReturn (true );
714+
705715 UniverseDefinitionTaskParams taskParams = new UniverseDefinitionTaskParams ();
706716 taskParams .universeDetached = false ;
717+
718+ // Set up primary cluster with valid version to ensure validation runs
719+ UniverseDefinitionTaskParams .UserIntent userIntent =
720+ new UniverseDefinitionTaskParams .UserIntent ();
721+ userIntent .ybSoftwareVersion = "2024.2.0.0-b1" ; // Valid version for attach/detach
722+ taskParams .upsertPrimaryCluster (userIntent , null );
723+
707724 when (universe .getUniverseDetails ()).thenReturn (taskParams );
725+ when (universe .getName ()).thenReturn ("test-universe" );
708726
709727 UUID currentYwUuid = UUID .randomUUID ();
710728 UUID storedYwUuid = UUID .randomUUID (); // Different UUID
@@ -736,9 +754,20 @@ public void testvalidateUniverseOwnershipAndNotDetached_ValidOwner() {
736754 YsqlQueryExecutor ysqlQueryExecutor = mock (YsqlQueryExecutor .class );
737755 RuntimeConfGetter confGetter = mock (RuntimeConfGetter .class );
738756
757+ // Mock attach/detach feature enabled
758+ when (confGetter .getGlobalConf (GlobalConfKeys .attachDetachEnabled )).thenReturn (true );
759+
739760 UniverseDefinitionTaskParams taskParams = new UniverseDefinitionTaskParams ();
740761 taskParams .universeDetached = false ;
762+
763+ // Set up primary cluster with valid version to ensure validation runs
764+ UniverseDefinitionTaskParams .UserIntent userIntent =
765+ new UniverseDefinitionTaskParams .UserIntent ();
766+ userIntent .ybSoftwareVersion = "2024.2.0.0-b1" ; // Valid version for attach/detach
767+ taskParams .upsertPrimaryCluster (userIntent , null );
768+
741769 when (universe .getUniverseDetails ()).thenReturn (taskParams );
770+ when (universe .getName ()).thenReturn ("test-universe" );
742771
743772 UUID currentYwUuid = UUID .randomUUID ();
744773 UUID storedYwUuid = currentYwUuid ; // Same UUID
@@ -754,4 +783,145 @@ public void testvalidateUniverseOwnershipAndNotDetached_ValidOwner() {
754783 Util .validateUniverseOwnershipAndNotDetached (
755784 universe , configHelper , ysqlQueryExecutor , confGetter );
756785 }
786+
787+ @ Test
788+ public void testIsUniverseOwner_DetachedUniverseUuid () {
789+ Universe universe = mock (Universe .class );
790+ ConfigHelper configHelper = mock (ConfigHelper .class );
791+ YsqlQueryExecutor ysqlQueryExecutor = mock (YsqlQueryExecutor .class );
792+ RuntimeConfGetter confGetter = mock (RuntimeConfGetter .class );
793+
794+ UUID currentYwUuid = UUID .randomUUID ();
795+ when (configHelper .getYugawareUUID ()).thenReturn (currentYwUuid );
796+ when (universe .getName ()).thenReturn ("test-universe" );
797+
798+ // Stored UUID is DETACHED_UNIVERSE_UUID (orphaned universe)
799+ YsqlQueryExecutor .ConsistencyInfoResp consistencyInfo =
800+ mock (YsqlQueryExecutor .ConsistencyInfoResp .class );
801+ when (consistencyInfo .getYwUUID ()).thenReturn (AttachDetachSpec .DETACHED_UNIVERSE_UUID );
802+ when (ysqlQueryExecutor .getConsistencyInfo (any (Universe .class ))).thenReturn (consistencyInfo );
803+
804+ PlatformServiceException exception =
805+ assertThrows (
806+ "Should throw exception for orphaned universe (DETACHED_UNIVERSE_UUID)" ,
807+ PlatformServiceException .class ,
808+ () -> Util .isUniverseOwner (universe , configHelper , ysqlQueryExecutor , confGetter ));
809+
810+ assertTrue (
811+ "Exception should mention orphaned universe" ,
812+ exception .getMessage ().contains ("has no owner" )
813+ && exception .getMessage ().contains ("orphaned" ));
814+ }
815+
816+ @ Test
817+ public void testvalidateUniverseOwnershipAndNotDetached_OldDbVersion () {
818+ Universe universe = mock (Universe .class );
819+ ConfigHelper configHelper = mock (ConfigHelper .class );
820+ YsqlQueryExecutor ysqlQueryExecutor = mock (YsqlQueryExecutor .class );
821+ RuntimeConfGetter confGetter = mock (RuntimeConfGetter .class );
822+
823+ // Mock attach/detach feature enabled
824+ when (confGetter .getGlobalConf (GlobalConfKeys .attachDetachEnabled )).thenReturn (true );
825+
826+ UniverseDefinitionTaskParams taskParams = new UniverseDefinitionTaskParams ();
827+ taskParams .universeDetached = false ;
828+
829+ // Set up universe with old DB version (before attach/detach support)
830+ UniverseDefinitionTaskParams .UserIntent userIntent =
831+ new UniverseDefinitionTaskParams .UserIntent ();
832+ userIntent .ybSoftwareVersion = "2.18.0.0-b1" ; // Old version
833+ taskParams .upsertPrimaryCluster (userIntent , null );
834+
835+ when (universe .getUniverseDetails ()).thenReturn (taskParams );
836+ when (universe .getName ()).thenReturn ("test-universe" );
837+
838+ // Should not throw - validation should be skipped for old versions
839+ Util .validateUniverseOwnershipAndNotDetached (
840+ universe , configHelper , ysqlQueryExecutor , confGetter );
841+ }
842+
843+ @ Test
844+ public void testvalidateUniverseOwnershipAndNotDetached_NullDbVersion () {
845+ Universe universe = mock (Universe .class );
846+ ConfigHelper configHelper = mock (ConfigHelper .class );
847+ YsqlQueryExecutor ysqlQueryExecutor = mock (YsqlQueryExecutor .class );
848+ RuntimeConfGetter confGetter = mock (RuntimeConfGetter .class );
849+
850+ // Mock attach/detach feature enabled
851+ when (confGetter .getGlobalConf (GlobalConfKeys .attachDetachEnabled )).thenReturn (true );
852+
853+ UniverseDefinitionTaskParams taskParams = new UniverseDefinitionTaskParams ();
854+ taskParams .universeDetached = false ;
855+
856+ // Set up universe with null DB version
857+ UniverseDefinitionTaskParams .UserIntent userIntent =
858+ new UniverseDefinitionTaskParams .UserIntent ();
859+ userIntent .ybSoftwareVersion = null ; // Null version
860+ taskParams .upsertPrimaryCluster (userIntent , null );
861+
862+ when (universe .getUniverseDetails ()).thenReturn (taskParams );
863+ when (universe .getName ()).thenReturn ("test-universe" );
864+
865+ // Should not throw - validation should be skipped for null versions
866+ Util .validateUniverseOwnershipAndNotDetached (
867+ universe , configHelper , ysqlQueryExecutor , confGetter );
868+ }
869+
870+ @ Test
871+ public void testvalidateUniverseOwnershipAndNotDetached_YcqlOnlyUniverse () {
872+ Universe universe = mock (Universe .class );
873+ ConfigHelper configHelper = mock (ConfigHelper .class );
874+ YsqlQueryExecutor ysqlQueryExecutor = mock (YsqlQueryExecutor .class );
875+ RuntimeConfGetter confGetter = mock (RuntimeConfGetter .class );
876+
877+ // Mock attach/detach feature enabled
878+ when (confGetter .getGlobalConf (GlobalConfKeys .attachDetachEnabled )).thenReturn (true );
879+
880+ UniverseDefinitionTaskParams taskParams = new UniverseDefinitionTaskParams ();
881+ taskParams .universeDetached = false ;
882+
883+ // Set up YCQL-only universe (YSQL disabled)
884+ UniverseDefinitionTaskParams .UserIntent userIntent =
885+ new UniverseDefinitionTaskParams .UserIntent ();
886+ userIntent .ybSoftwareVersion = "2024.2.0.0-b1" ; // Valid version
887+ userIntent .enableYSQL = false ; // YCQL-only
888+ taskParams .upsertPrimaryCluster (userIntent , null );
889+
890+ when (universe .getUniverseDetails ()).thenReturn (taskParams );
891+ when (universe .getName ()).thenReturn ("test-universe" );
892+
893+ // Mock configHelper to return a valid UUID (needed if validation progresses to isUniverseOwner)
894+ when (configHelper .getYugawareUUID ()).thenReturn (UUID .randomUUID ());
895+
896+ // Should not throw - validation should be skipped for YCQL-only universes
897+ Util .validateUniverseOwnershipAndNotDetached (
898+ universe , configHelper , ysqlQueryExecutor , confGetter );
899+ }
900+
901+ @ Test
902+ public void testvalidateUniverseOwnershipAndNotDetached_FeatureDisabled () {
903+ Universe universe = mock (Universe .class );
904+ ConfigHelper configHelper = mock (ConfigHelper .class );
905+ YsqlQueryExecutor ysqlQueryExecutor = mock (YsqlQueryExecutor .class );
906+ RuntimeConfGetter confGetter = mock (RuntimeConfGetter .class );
907+
908+ // Mock attach/detach feature DISABLED
909+ when (confGetter .getGlobalConf (GlobalConfKeys .attachDetachEnabled )).thenReturn (false );
910+
911+ UniverseDefinitionTaskParams taskParams = new UniverseDefinitionTaskParams ();
912+ taskParams .universeDetached = true ;
913+
914+ // Set up universe with valid version
915+ UniverseDefinitionTaskParams .UserIntent userIntent =
916+ new UniverseDefinitionTaskParams .UserIntent ();
917+ userIntent .ybSoftwareVersion = "2024.2.0.0-b1" ;
918+ taskParams .upsertPrimaryCluster (userIntent , null );
919+
920+ when (universe .getUniverseDetails ()).thenReturn (taskParams );
921+ when (universe .getName ()).thenReturn ("test-universe" );
922+
923+ // validation should be skipped entirely when feature is disabled
924+ Util .validateUniverseOwnershipAndNotDetached (
925+ universe , configHelper , ysqlQueryExecutor , confGetter );
926+ }
757927}
0 commit comments