Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issv3: add xmlrpc hub apis #9866

Open
wants to merge 11 commits into
base: issv3-2
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2025 SUSE LLC
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*/

package com.redhat.rhn.frontend.xmlrpc.serializer;

import com.suse.manager.api.ApiResponseSerializer;
import com.suse.manager.api.SerializationBuilder;
import com.suse.manager.api.SerializedApiResponse;
import com.suse.manager.model.hub.ManagerInfoJson;

public class ManagerInfoSerializer extends ApiResponseSerializer<ManagerInfoJson> {

@Override
public Class<ManagerInfoJson> getSupportedClass() {
return ManagerInfoJson.class;
}

@Override
public SerializedApiResponse serialize(ManagerInfoJson src) {
return new SerializationBuilder()
.add("version", src.getVersion())
.add("reportDb", src.hasReportDb())
.add("reportDbName", src.getReportDbName())
.add("reportDbHost", src.getReportDbHost())
.add("reportDbPort", src.getReportDbPort())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ private SerializerRegistry() {
SERIALIZER_CLASSES.add(AppStreamModuleResponseSerializer.class);
SERIALIZER_CLASSES.add(AppStreamSerializer.class);
SERIALIZER_CLASSES.add(UserNotificationSerializer.class);
SERIALIZER_CLASSES.add(ManagerInfoSerializer.class);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
package com.suse.manager.hub;

import com.redhat.rhn.common.util.http.HttpClientAdapter;
import com.redhat.rhn.domain.channel.Channel;
import com.redhat.rhn.domain.org.Org;

import com.suse.manager.model.hub.ManagerInfoJson;
import com.suse.manager.model.hub.RegisterJson;
import com.suse.manager.model.hub.SCCCredentialsJson;
import com.suse.manager.webui.controllers.ECMAScriptDateAdapter;
import com.suse.manager.webui.utils.gson.ResultJson;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Expand All @@ -34,8 +37,10 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -102,6 +107,16 @@ public void deregister() throws IOException {
invokePost("hub/sync", "deregister", null);
}

@Override
public List<Org> listAllPeripheralOrgs() throws IOException {
return invokeGet("hub", "listAllPeripheralOrgs", List.class);
}

@Override
public List<Channel> listAllPeripheralChannels() throws IOException {
return invokeGet("hub", "listAllPeripheralChannels", List.class);
}

private <R> R invokeGet(String namespace, String apiMethod, Class<R> responseClass)
throws IOException {
return invoke(HttpGet.METHOD_NAME, namespace, apiMethod, null, responseClass);
Expand Down Expand Up @@ -133,7 +148,10 @@ private <T, R> R invoke(String httpMethod, String namespace, String apiMethod, T
int statusCode = response.getStatusLine().getStatusCode();
// Ensure we get a valid response
if (statusCode != HttpStatus.SC_OK) {
throw new InvalidResponseException("Unexpected response code %d".formatted(statusCode));
String responseBody = new String(response.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
ResultJson<?> responseJson = GSON.fromJson(responseBody, ResultJson.class);
String errorMessage = String.join(", ", responseJson.getMessages());
throw new InvalidResponseException("Unexpected response code %d %s".formatted(statusCode, errorMessage));
}

// Parse the response object, if specified
Expand Down
18 changes: 18 additions & 0 deletions java/code/src/com/suse/manager/hub/HubInternalClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@

package com.suse.manager.hub;

import com.redhat.rhn.domain.channel.Channel;
import com.redhat.rhn.domain.org.Org;

import com.suse.manager.model.hub.ManagerInfoJson;

import java.io.IOException;
import java.util.List;

/**
* Hub Inter-Server-Sync Client to connect a remote server and invoke the private server-to-server Rest-like API
Expand Down Expand Up @@ -72,4 +76,18 @@ public interface HubInternalClient {
* @throws IOException when the communication fails
*/
void scheduleProductRefresh() throws IOException;

/**
* Collect data about all organizations on the remote peripheral server
*
* @return return list of {@link Org}
*/
List<Org> listAllPeripheralOrgs() throws IOException;

/**
* Collect data about all channels on the remote peripheral server
*
* @return return list of {@link Channel}
*/
List<Channel> listAllPeripheralChannels() throws IOException;
}
81 changes: 75 additions & 6 deletions java/code/src/com/suse/manager/hub/HubManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.redhat.rhn.GlobalInstanceHolder;
import com.redhat.rhn.common.conf.Config;
import com.redhat.rhn.common.conf.ConfigDefaults;
import com.redhat.rhn.common.hibernate.HibernateFactory;
import com.redhat.rhn.common.security.PermissionException;
import com.redhat.rhn.domain.channel.Channel;
import com.redhat.rhn.domain.channel.ChannelFactory;
Expand Down Expand Up @@ -289,7 +290,9 @@ private void deletePeripheral(String peripheralFqdn) {
}

private void deletePeripheral(IssPeripheral peripheral) {
CredentialsFactory.removeCredentials(peripheral.getMirrorCredentials());
if (null != peripheral.getMirrorCredentials()) {
CredentialsFactory.removeCredentials(peripheral.getMirrorCredentials());
}
hubFactory.remove(peripheral);
hubFactory.removeAccessTokensFor(peripheral.getFqdn());
}
Expand Down Expand Up @@ -520,6 +523,17 @@ public ManagerInfoJson collectManagerInfo(IssAccessToken accessToken) {
return collectManagerInfo();
}

/**
* Collect data about a Manager Server
*
* @param user The current user
* @return return {@link ManagerInfoJson}
*/
public ManagerInfoJson collectManagerInfo(User user) {
ensureSatAdmin(user);
return collectManagerInfo();
}

/**
* Set server details
*
Expand Down Expand Up @@ -559,7 +573,7 @@ private void updateServerData(String fqdn, IssRole role, Map<String, String> dat
},
() -> {
LOG.error("Server {} not found with role {}", fqdn, role);
throw new IllegalArgumentException("Server not found");
throw new IllegalArgumentException("Server %s not found with role %s".formatted(fqdn, role));
});
case PERIPHERAL -> hubFactory.lookupIssPeripheralByFqdn(fqdn).ifPresentOrElse(issPeripheral -> {
if (data.containsKey("root_ca")) {
Expand All @@ -569,11 +583,11 @@ private void updateServerData(String fqdn, IssRole role, Map<String, String> dat
},
()-> {
LOG.error("Server {} not found with role {}", fqdn, role);
throw new IllegalArgumentException("Server not found");
throw new IllegalArgumentException("Server %s not found with role %s".formatted(fqdn, role));
});
default -> {
LOG.error("Unknown role {}", role);
throw new IllegalArgumentException("Unknown role");
throw new IllegalArgumentException("Unknown role %s".formatted(role));
}
}
}
Expand Down Expand Up @@ -684,12 +698,28 @@ private void registerToRemote(User user, IssServer remoteServer, String remoteTo
internalApi.scheduleProductRefresh();
}
catch (Exception ex) {
// cleanup the remote side
internalApi.deregister();
cleanup(internalApi);
throw ex;
}
}

private void cleanup(HubInternalClient internalApi) {
// cleanup the local side: explicit rollback
// HubManager.createServer has already created an ISSPeripheral object
//
// Explicit rollback is needed since SparkApplicationHelper.setupHibernateSessionFilter sets Spark.after
// which in turn calls HibernateFactory.commitTransaction() if there's an ongoing transaction
HibernateFactory.rollbackTransaction();

try {
// try to cleanup the remote side
internalApi.deregister();
}
catch (Exception ex) {
//
}
}

private void ensureServerNotRegistered(String peripheralServer) {
Optional<IssHub> issHub = hubFactory.lookupIssHubByFqdn(peripheralServer);
if (issHub.isPresent()) {
Expand Down Expand Up @@ -892,6 +922,45 @@ private Server getOrCreateManagerSystem(
return server;
}

/**
* Collect data about all peripheral organizations
*
* @param user The current user
* @param peripheralFqdn the remote peripheral server FQDN
* @return return list of {@link Org}
*/
public List<Org> listAllPeripheralOrgs(User user, String peripheralFqdn) throws IOException, CertificateException {
ensureSatAdmin(user);

IssPeripheral issPeripheral = hubFactory.lookupIssPeripheralByFqdn(peripheralFqdn).orElseThrow(() ->
new IllegalStateException("Server " + peripheralFqdn + " is not registered as peripheral"));

IssAccessToken accessToken = hubFactory.lookupAccessTokenFor(issPeripheral.getFqdn());
var internalClient = clientFactory.newInternalClient(issPeripheral.getFqdn(), accessToken.getToken(),
issPeripheral.getRootCa());
return internalClient.listAllPeripheralOrgs();
}

/**
* Collect data about all peripheral channels
*
* @param user The current user
* @param peripheralFqdn the remote peripheral server FQDN
* @return return list of {@link Channel}
*/
public List<Channel> listAllPeripheralChannels(User user, String peripheralFqdn)
throws IOException, CertificateException {
ensureSatAdmin(user);

IssPeripheral issPeripheral = hubFactory.lookupIssPeripheralByFqdn(peripheralFqdn).orElseThrow(() ->
new IllegalStateException("Server " + peripheralFqdn + " is not registered as peripheral"));

IssAccessToken accessToken = hubFactory.lookupAccessTokenFor(issPeripheral.getFqdn());
var internalClient = clientFactory.newInternalClient(issPeripheral.getFqdn(), accessToken.getToken(),
issPeripheral.getRootCa());
return internalClient.listAllPeripheralChannels();
}

/**
* Collect data about all organizations
*
Expand Down
Loading
Loading