Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -7603,6 +7604,114 @@ public List<Map<String, Object>> 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<Map<String, Object>> listMigrationTargetsWithChannels(User loggedInUser, Integer sid) {
List<Map<String, Object>> returnList = new ArrayList<>();
Server server = lookupServer(loggedInUser, sid);
Optional<SUSEProductSet> installedProducts = server.getInstalledProductSet();
if (!installedProducts.isPresent()) {
throw new FaultException(-1, "listMigrationTargetError",
"Server has no Products installed.");
}
ChannelArch arch = server.getServerArch().getCompatibleChannelArch();
List<SUSEProductSet> migrationTargets = DistUpgradeManager.
getTargetProductSets(installedProducts, arch, loggedInUser);

for (SUSEProductSet target : migrationTargets) {
if (!target.getIsEveryChannelSynced()) {
continue;
}

Map<String, Object> targetMap = new HashMap<>();
targetMap.put("ident", target.getSerializedProductIDs());
targetMap.put("friendly", target.toString());

List<Map<String, Object>> 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<EssentialChannelDto> requiredChannels = DistUpgradeManager.getRequiredChannels(
target, baseChannel.getId());
List<Long> requiredChannelIds = requiredChannels.stream()
.map(EssentialChannelDto::getId)
.collect(toList());

channelOptions.add(buildChannelOptionMap(baseChannel, loggedInUser, requiredChannelIds));
}

// 2. Alternative Base Channels (Clones)
SortedMap<ClonedChannel, List<Long>> alternatives = DistUpgradeManager.getAlternatives(
target, arch, loggedInUser);

for (Map.Entry<ClonedChannel, List<Long>> entry : alternatives.entrySet()) {
channelOptions.add(buildChannelOptionMap(entry.getKey(), loggedInUser, entry.getValue()));
}

targetMap.put("channel_options", channelOptions);
returnList.add(targetMap);
}

return returnList;
}

private Map<String, Object> buildChannelOptionMap(Channel baseChannel, User user, List<Long> requiredChannelIds) {
Map<String, Object> optionMap = new HashMap<>();
optionMap.put("base_channel_label", baseChannel.getLabel());
optionMap.put("base_channel_name", baseChannel.getName());

List<Map<String, Object>> childChannelsList = new ArrayList<>();
List<Channel> accessibleChildren = baseChannel.getAccessibleChildrenFor(user);

// Sort by name
accessibleChildren.sort(Comparator.comparing(Channel::getName));

for (Channel child : accessibleChildren) {
Map<String, Object> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -3771,4 +3779,129 @@ private SystemHandler getMockedHandler() throws Exception {

return systemHandler;
}

@Test
public void testListMigrationTargetsWithChannels() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is quite long: I would split it in one or more setup phases to better highlight what you are going to test

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);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable "e" is never used and SonarCloud is complaining about it.
I agree that writing a line like new SUSEProductExtension( sourceAddonProduct, sourceBaseProduct, false); just because the creation of such object has a side effect makes the intent un-understantable.
So I suggest you make a "not strictly necessary" check afterwards, something like assertNotNull(e); so SonarCloud does not complain and the assignment makes sense

SUSEProductTestUtils.populateRepository(sourceBaseProduct, sourceBaseChannel, sourceBaseProduct,
sourceBaseChannel, admin);

sourceBaseChannel = ChannelFactory.lookupById(sourceBaseChannel.getId());
sourceChildChannel = ChannelFactory.lookupById(sourceChildChannel.getId());

Set<Channel> channels = new HashSet<>();
channels.add(sourceBaseChannel);
channels.add(sourceChildChannel);
Server server = ServerFactoryTest.createTestServer(admin);
server.setChannels(channels);

Set<InstalledProduct> 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether use TestUtils.saveAndFlush, or do targetBaseChannel = 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're not reusing e2 and e3, TestUtils.saveAndFlush is sufficient. Otherwise you have to do e2 = TestUtils.saveAndReload(e2);


SUSEProductTestUtils.populateRepository(targetBaseProduct, targetBaseChannel, targetBaseProduct,
targetBaseChannel, admin);

List<Map<String, Object>> result = handler.listMigrationTargetsWithChannels(admin, server.getId().intValue());
assertNotNull(result);
assertFalse(result.isEmpty());
boolean found = false;
for (Map<String, Object> target : result) {
List<Map<String, Object>> channelOptions = (List<Map<String, Object>>) target.get("channel_options");
for (Map<String, Object> option : channelOptions) {
List<Map<String, Object>> children = (List<Map<String, Object>>) option.get("child_channels");
for (Map<String, Object> 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");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Added listMigrationTargetsWithChannels endpoint to return
the valid migration targets channels (jsc#SUMA-320)