diff --git a/java/core/src/main/java/com/redhat/rhn/frontend/xmlrpc/system/SystemHandler.java b/java/core/src/main/java/com/redhat/rhn/frontend/xmlrpc/system/SystemHandler.java index 8f365391da93..133bb10e884b 100644 --- a/java/core/src/main/java/com/redhat/rhn/frontend/xmlrpc/system/SystemHandler.java +++ b/java/core/src/main/java/com/redhat/rhn/frontend/xmlrpc/system/SystemHandler.java @@ -216,6 +216,7 @@ import java.util.Calendar; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -7603,6 +7604,114 @@ public List> listMigrationTargets(User loggedInUser, return returnList; } + /** + * Lists the valid migration targets for a given server, including channel details. + * @param loggedInUser The current user + * @param sid The server ID + * @return List of maps containing migration targets and their channel options + * @throws FaultException A FaultException is thrown if the server corresponding to + * sid cannot be found or if the server has no products installed. + * + * @apidoc.doc Lists the eligible migration targets for a given server, including channel details. + * @apidoc.param #session_key() + * @apidoc.param #param("int", "sid") + * @apidoc.returntype + * #return_array_begin() + * #struct_begin("migration target") + * #prop("string", "ident", "Product IDs for the target product set ( e.g. '[1894, 1905]')") + * #prop("string", "friendly", "Friendly name of the target product set") + * #prop_array_begin("channel_options") + * #struct_begin("channel option") + * #prop("string", "base_channel_label", "Label of the base channel") + * #prop("string", "base_channel_name", "Name of the base channel") + * #prop_array_begin("child_channels") + * #struct_begin("child channel") + * #prop("string", "label", "Channel label") + * #prop("string", "name", "Channel name") + * #prop("boolean", "mandatory", "Whether the channel is mandatory") + * #struct_end() + * #array_end() + * #struct_end() + * #array_end() + * #struct_end() + * #array_end() + */ + public List> listMigrationTargetsWithChannels(User loggedInUser, Integer sid) { + List> returnList = new ArrayList<>(); + Server server = lookupServer(loggedInUser, sid); + Optional installedProducts = server.getInstalledProductSet(); + if (!installedProducts.isPresent()) { + throw new FaultException(-1, "listMigrationTargetError", + "Server has no Products installed."); + } + ChannelArch arch = server.getServerArch().getCompatibleChannelArch(); + List migrationTargets = DistUpgradeManager. + getTargetProductSets(installedProducts, arch, loggedInUser); + + for (SUSEProductSet target : migrationTargets) { + if (!target.getIsEveryChannelSynced()) { + continue; + } + + Map targetMap = new HashMap<>(); + targetMap.put("ident", target.getSerializedProductIDs()); + targetMap.put("friendly", target.toString()); + + List> channelOptions = new ArrayList<>(); + + // 1. Default Base Channel + Channel baseChannel = DistUpgradeManager.getProductBaseChannel( + target.getBaseProduct().getId(), arch, loggedInUser); + + if (baseChannel != null) { + // Get required child channels for this target/base pairing + List requiredChannels = DistUpgradeManager.getRequiredChannels( + target, baseChannel.getId()); + List requiredChannelIds = requiredChannels.stream() + .map(EssentialChannelDto::getId) + .collect(toList()); + + channelOptions.add(buildChannelOptionMap(baseChannel, loggedInUser, requiredChannelIds)); + } + + // 2. Alternative Base Channels (Clones) + SortedMap> alternatives = DistUpgradeManager.getAlternatives( + target, arch, loggedInUser); + + for (Map.Entry> entry : alternatives.entrySet()) { + channelOptions.add(buildChannelOptionMap(entry.getKey(), loggedInUser, entry.getValue())); + } + + targetMap.put("channel_options", channelOptions); + returnList.add(targetMap); + } + + return returnList; + } + + private Map buildChannelOptionMap(Channel baseChannel, User user, List requiredChannelIds) { + Map optionMap = new HashMap<>(); + optionMap.put("base_channel_label", baseChannel.getLabel()); + optionMap.put("base_channel_name", baseChannel.getName()); + + List> childChannelsList = new ArrayList<>(); + List accessibleChildren = baseChannel.getAccessibleChildrenFor(user); + + // Sort by name + accessibleChildren.sort(Comparator.comparing(Channel::getName)); + + for (Channel child : accessibleChildren) { + Map childMap = new HashMap<>(); + childMap.put("label", child.getLabel()); + childMap.put("name", child.getName()); + childMap.put("mandatory", requiredChannelIds.contains(child.getId())); + childChannelsList.add(childMap); + } + + optionMap.put("child_channels", childChannelsList); + return optionMap; + } + /** * Schedule a Product migration for a system. This call is the recommended and * supported way of migrating a system to the next Service Pack. diff --git a/java/core/src/test/java/com/redhat/rhn/frontend/xmlrpc/system/test/SystemHandlerTest.java b/java/core/src/test/java/com/redhat/rhn/frontend/xmlrpc/system/test/SystemHandlerTest.java index 23b86632ca52..49c447578d1e 100644 --- a/java/core/src/test/java/com/redhat/rhn/frontend/xmlrpc/system/test/SystemHandlerTest.java +++ b/java/core/src/test/java/com/redhat/rhn/frontend/xmlrpc/system/test/SystemHandlerTest.java @@ -53,7 +53,9 @@ import com.redhat.rhn.domain.channel.Channel; import com.redhat.rhn.domain.channel.ChannelFactory; import com.redhat.rhn.domain.channel.ChannelFamily; +import com.redhat.rhn.domain.channel.ChannelFamilyFactory; import com.redhat.rhn.domain.channel.ChannelTestUtility; +import com.redhat.rhn.domain.channel.PublicChannelFamily; import com.redhat.rhn.domain.channel.test.ChannelFactoryTest; import com.redhat.rhn.domain.entitlement.Entitlement; import com.redhat.rhn.domain.errata.Errata; @@ -65,7 +67,10 @@ import com.redhat.rhn.domain.org.CustomDataKey; import com.redhat.rhn.domain.org.Org; import com.redhat.rhn.domain.org.test.CustomDataKeyTest; +import com.redhat.rhn.domain.product.ChannelTemplate; import com.redhat.rhn.domain.product.SUSEProduct; +import com.redhat.rhn.domain.product.SUSEProductChannel; +import com.redhat.rhn.domain.product.SUSEProductExtension; import com.redhat.rhn.domain.product.SUSEProductFactory; import com.redhat.rhn.domain.product.test.SUSEProductTestUtils; import com.redhat.rhn.domain.rhnpackage.Package; @@ -75,6 +80,9 @@ import com.redhat.rhn.domain.rhnpackage.profile.ProfileFactory; import com.redhat.rhn.domain.rhnpackage.test.PackageTest; import com.redhat.rhn.domain.role.RoleFactory; +import com.redhat.rhn.domain.scc.SCCCachingFactory; +import com.redhat.rhn.domain.scc.SCCRepository; +import com.redhat.rhn.domain.scc.SCCRepositoryNoAuth; import com.redhat.rhn.domain.server.CPU; import com.redhat.rhn.domain.server.CustomDataValue; import com.redhat.rhn.domain.server.Device; @@ -3771,4 +3779,129 @@ private SystemHandler getMockedHandler() throws Exception { return systemHandler; } + + @Test + public void testListMigrationTargetsWithChannels() throws Exception { + SUSEProductTestUtils.createSCCCredentials("dummy", admin); + // Setup source products + ChannelFamily family = createTestChannelFamily(); + SUSEProduct sourceBaseProduct = SUSEProductTestUtils.createTestSUSEProduct(family); + Channel sourceBaseChannel = SUSEProductTestUtils.createBaseChannelForBaseProduct(sourceBaseProduct, admin); + sourceBaseChannel.setChannelArch(ChannelFactory.findArchByLabel("channel-x86_64")); + + SUSEProduct sourceAddonProduct = SUSEProductTestUtils.createTestSUSEProduct(family); + sourceAddonProduct.setBase(false); + Channel sourceChildChannel = SUSEProductTestUtils.createChildChannelsForProduct( + sourceAddonProduct, sourceBaseChannel, admin); + + SUSEProductTestUtils.populateRepository(sourceBaseProduct, sourceBaseChannel, sourceBaseProduct, + sourceBaseChannel, admin); + + sourceBaseChannel = ChannelFactory.lookupById(sourceBaseChannel.getId()); + sourceChildChannel = ChannelFactory.lookupById(sourceChildChannel.getId()); + + Set channels = new HashSet<>(); + channels.add(sourceBaseChannel); + channels.add(sourceChildChannel); + Server server = ServerFactoryTest.createTestServer(admin); + server.setChannels(channels); + + Set installedProductSet = new java.util.HashSet<>(); + installedProductSet.add(SUSEProductTestUtils.getInstalledProduct(sourceBaseProduct)); + installedProductSet.add(SUSEProductTestUtils.getInstalledProduct(sourceAddonProduct)); + server.setInstalledProducts(installedProductSet); + + TestUtils.saveAndFlush(server); + + // Setup migration target product + upgrade path + SUSEProduct targetBaseProduct = SUSEProductTestUtils.createTestSUSEProduct(family); + Channel targetBaseChannel = SUSEProductTestUtils.createBaseChannelForBaseProduct(targetBaseProduct, admin); + targetBaseChannel.setChannelArch(ChannelFactory.findArchByLabel("channel-x86_64")); + TestUtils.saveAndReload(targetBaseChannel); + sourceBaseProduct.setUpgrades(Collections.singleton(targetBaseProduct)); + + // Setup target addon product + upgrade path + SUSEProduct targetAddonProduct = SUSEProductTestUtils.createTestSUSEProduct(family); + targetAddonProduct.setBase(false); + TestUtils.saveAndFlush(targetAddonProduct); + sourceAddonProduct.setUpgrades(Collections.singleton(targetAddonProduct)); + TestUtils.saveAndFlush(sourceAddonProduct); + + Channel targetAddonChannel = + SUSEProductTestUtils.createChildChannelsForProduct(targetAddonProduct, targetBaseChannel, admin); + // 1. Update SUSEProductChannel to be mandatory (for SystemHandler) + targetAddonProduct = SUSEProductFactory.getProductById(targetAddonProduct.getId()); + for (SUSEProductChannel spc : targetAddonProduct.getSuseProductChannels()) { + if (spc.getChannel().getId().equals(targetAddonChannel.getId())) { + spc.setMandatory(true); + SUSEProductFactory.save(spc); + } + } + TestUtils.saveAndFlush(targetAddonProduct); + // 2. Setup ChannelTemplate and SCCRepository (for DistUpgradeManager availability check) + ChannelFamily cf = targetAddonProduct.getChannelFamily(); + if (cf.getPublicChannelFamily() == null) { + PublicChannelFamily pcf = new PublicChannelFamily(); + pcf.setChannelFamily(cf); + ChannelFamilyFactory.save(pcf); + cf.setPublicChannelFamily(pcf); + ChannelFamilyFactory.save(cf); + } + + SCCRepository repo = new SCCRepository(); + repo.setUrl("http://example.com/repo"); + repo.setName("test-repo"); + repo.setSccId(12345L); + repo.setDescription("Test Repository"); + repo.setDistroTarget("x86_64"); + HibernateFactory.getSession().save(repo); + + SCCRepositoryNoAuth auth = new SCCRepositoryNoAuth(); + auth.setRepo(repo); + SCCCachingFactory.saveRepositoryAuth(auth); + + repo.getRepositoryAuth().add(auth); + + ChannelTemplate ct = new ChannelTemplate(); + ct.setProduct(targetAddonProduct); + ct.setChannelLabel(targetAddonChannel.getLabel()); + ct.setMandatory(true); + ct.setRootProduct(targetBaseProduct); + ct.setChannelName(targetAddonChannel.getName()); + ct.setRepository(repo); + SUSEProductFactory.save(ct); + + targetAddonProduct.getChannelTemplates().add(ct); + TestUtils.saveAndFlush(targetAddonProduct); + + sourceAddonProduct.setUpgrades(Collections.singleton(targetAddonProduct)); + SUSEProductExtension e2 = new SUSEProductExtension( + sourceBaseProduct, targetAddonProduct, sourceBaseProduct, false); + SUSEProductExtension e3 = new SUSEProductExtension( + targetBaseProduct, targetAddonProduct, targetBaseProduct, false); + TestUtils.saveAndReload(e2); + TestUtils.saveAndReload(e3); + + SUSEProductTestUtils.populateRepository(targetBaseProduct, targetBaseChannel, targetBaseProduct, + targetBaseChannel, admin); + + List> result = handler.listMigrationTargetsWithChannels(admin, server.getId().intValue()); + assertNotNull(result); + assertFalse(result.isEmpty()); + boolean found = false; + for (Map target : result) { + List> channelOptions = (List>) target.get("channel_options"); + for (Map option : channelOptions) { + List> children = (List>) option.get("child_channels"); + for (Map child : children) { + if (child.get("name").equals(targetAddonChannel.getName())) { + if (Boolean.TRUE.equals(child.get("mandatory"))) { + found = true; + } + } + } + } + } + assertTrue(found, "Expected mandatory channel not found in results"); + } } diff --git a/java/spacewalk-java.changes.abid.add-api-endpoint-to-list-target-channels b/java/spacewalk-java.changes.abid.add-api-endpoint-to-list-target-channels new file mode 100644 index 000000000000..8019580715c1 --- /dev/null +++ b/java/spacewalk-java.changes.abid.add-api-endpoint-to-list-target-channels @@ -0,0 +1,2 @@ +- Added listMigrationTargetsWithChannels endpoint to return + the valid migration targets channels (jsc#SUMA-320)