Skip to content

Commit 34a6f34

Browse files
authored
Merge pull request #271 from SolaceProducts/moodiRealist/DATAGO-108479-reduce-logLevel
DATAGO-108479: Convert error logs to warning for connection issues that EMA is not at fault
2 parents 7f43203 + f77384c commit 34a6f34

File tree

8 files changed

+750
-39
lines changed

8 files changed

+750
-39
lines changed

service/application/src/main/java/com/solace/maas/ep/event/management/agent/command/SempGetCommandManager.java

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
@Service
2727
@Slf4j
2828
public class SempGetCommandManager extends AbstractSempCommandManager {
29+
private static final String SEMP_COMMAND_NOT_EXECUTED_SUCCESSFULLY = "SEMP {} command not executed successfully";
30+
2931
private final ObjectMapper objectMapper;
3032

3133
public SempGetCommandManager(ObjectMapper objectMapper) {
@@ -64,13 +66,21 @@ public void execute(Command command, SempApiProvider sempApiProvider) {
6466
if (SempEntityType.solaceClientProfileName.name().equals(entityType) &&
6567
isResourceNotFoundError(e)) {
6668
handleClientProfileNotFound(command);
69+
} else if (isConnectTimeoutError(e) || isNameResolutionError(e)) {
70+
log.warn(SEMP_COMMAND_NOT_EXECUTED_SUCCESSFULLY, supportedSempCommand(), e);
71+
setCommandError(command, e);
6772
} else {
68-
log.error("SEMP {} command not executed successfully", supportedSempCommand(), e);
73+
log.error(SEMP_COMMAND_NOT_EXECUTED_SUCCESSFULLY, supportedSempCommand(), e);
6974
setCommandError(command, e);
7075
}
7176
} catch (Exception e) {
72-
log.error("SEMP {} command not executed successfully", supportedSempCommand(), e);
73-
setCommandError(command, e);
77+
if (isConnectTimeoutError(e) || isNameResolutionError(e)) {
78+
log.warn(SEMP_COMMAND_NOT_EXECUTED_SUCCESSFULLY, supportedSempCommand(), e);
79+
setCommandError(command, e);
80+
} else {
81+
log.error(SEMP_COMMAND_NOT_EXECUTED_SUCCESSFULLY, supportedSempCommand(), e);
82+
setCommandError(command, e);
83+
}
7484
}
7585
}
7686

@@ -79,6 +89,58 @@ private boolean isResourceNotFoundError(ApiException e) {
7989
(e.getCode() == 400 && StringUtils.contains(e.getResponseBody(), "NOT_FOUND"));
8090
}
8191

92+
/**
93+
* Checks if the exception represents a connection timeout error.
94+
* This method examines the exception message and cause chain for timeout-related errors.
95+
*/
96+
private boolean isConnectTimeoutError(Exception e) {
97+
// Check the exception message and cause chain for timeout indicators
98+
String message = e.getMessage();
99+
if (message != null && message.contains("Connect timed out")) {
100+
return true;
101+
}
102+
103+
// Check the cause chain for SocketTimeoutException
104+
Throwable cause = e.getCause();
105+
while (cause != null) {
106+
if (cause instanceof java.net.SocketTimeoutException) {
107+
String causeMessage = cause.getMessage();
108+
if (causeMessage != null && causeMessage.contains("Connect timed out")) {
109+
return true;
110+
}
111+
}
112+
cause = cause.getCause();
113+
}
114+
115+
return false;
116+
}
117+
118+
/**
119+
* Checks if the exception represents a name resolution error.
120+
* This method examines the exception message and cause chain for DNS resolution failures.
121+
*/
122+
private boolean isNameResolutionError(Exception e) {
123+
// Check the exception message for name resolution indicators
124+
String message = e.getMessage();
125+
if (message != null && message.contains("Name does not resolve")) {
126+
return true;
127+
}
128+
129+
// Check the cause chain for UnknownHostException with name resolution errors
130+
Throwable cause = e.getCause();
131+
while (cause != null) {
132+
if (cause instanceof java.net.UnknownHostException) {
133+
String causeMessage = cause.getMessage();
134+
if (causeMessage != null && causeMessage.contains("Name does not resolve")) {
135+
return true;
136+
}
137+
}
138+
cause = cause.getCause();
139+
}
140+
141+
return false;
142+
}
143+
82144
private void handleClientProfileNotFound(Command command) {
83145
String resourceName = extractClientProfileName(command);
84146
String errorMessage = String.format("Named client profile not found on the event broker: %s", resourceName);

service/application/src/test/java/com/solace/maas/ep/event/management/agent/commandManager/SempGetCommandManagerTest.java

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
2121

2222
import java.io.IOException;
23+
import java.net.SocketTimeoutException;
24+
import java.net.UnknownHostException;
2325
import java.nio.charset.StandardCharsets;
2426
import java.nio.file.Files;
2527
import java.nio.file.Path;
@@ -244,6 +246,113 @@ void with400Exception() throws ApiException {
244246
assertThat(cmd.getResult().getStatus()).isEqualTo(JobStatus.validation_error);
245247
}
246248

249+
@Test
250+
void withConnectTimeoutApiException() throws ApiException {
251+
// Create an ApiException with SocketTimeoutException as cause
252+
SocketTimeoutException timeoutException = new SocketTimeoutException("Connect timed out");
253+
ApiException apiException = new ApiException(timeoutException);
254+
255+
executeTestWithException(apiException);
256+
}
257+
258+
@Test
259+
void withConnectTimeoutExceptionMessage() throws ApiException {
260+
// Create an ApiException with "Connect timed out" in the message
261+
ApiException apiException = new ApiException("Connect timed out");
262+
263+
executeTestWithException(apiException);
264+
}
265+
266+
@Test
267+
void withNestedConnectTimeoutException() throws ApiException {
268+
// Create nested exception with SocketTimeoutException deep in the cause chain
269+
SocketTimeoutException timeoutException = new SocketTimeoutException("Connect timed out");
270+
RuntimeException wrapperException = new RuntimeException("Wrapper exception", timeoutException);
271+
ApiException apiException = new ApiException(wrapperException);
272+
273+
executeTestWithException(apiException);
274+
}
275+
276+
@Test
277+
void withNonTimeoutSocketTimeoutException() throws ApiException {
278+
// Create SocketTimeoutException with different message (not connect timeout)
279+
SocketTimeoutException timeoutException = new SocketTimeoutException("Read timed out");
280+
ApiException apiException = new ApiException(timeoutException);
281+
282+
executeTestWithException(apiException);
283+
}
284+
285+
@Test
286+
void withGeneralExceptionConnectTimeout() throws ApiException {
287+
// Create a general Exception (not ApiException) with connect timeout
288+
RuntimeException generalException = new RuntimeException("Connect timed out");
289+
290+
executeTestWithException(generalException);
291+
}
292+
293+
@Test
294+
void withNameResolutionApiException() throws ApiException {
295+
// Create an ApiException with UnknownHostException as cause containing "Name does not resolve"
296+
UnknownHostException nameResolutionException = new UnknownHostException("msg-solace-test.lcag-cmlz-n.lhgroup.de: Name does not resolve");
297+
ApiException apiException = new ApiException(nameResolutionException);
298+
299+
executeTestWithException(apiException);
300+
}
301+
302+
@Test
303+
void withNameResolutionExceptionMessage() throws ApiException {
304+
// Create an ApiException with "Name does not resolve" in the message
305+
ApiException apiException = new ApiException("java.net.UnknownHostException: msg-solace-test.lcag-cmlz-n.lhgroup.de: Name does not resolve");
306+
307+
executeTestWithException(apiException);
308+
}
309+
310+
@Test
311+
void withNestedNameResolutionException() throws ApiException {
312+
// Create nested exception with UnknownHostException deep in the cause chain
313+
UnknownHostException nameResolutionException = new UnknownHostException("host.example.com: Name does not resolve");
314+
RuntimeException wrapperException = new RuntimeException("Wrapper exception", nameResolutionException);
315+
ApiException apiException = new ApiException(wrapperException);
316+
317+
executeTestWithException(apiException);
318+
}
319+
320+
@Test
321+
void withNonNameResolutionUnknownHostException() throws ApiException {
322+
// Create UnknownHostException with different message (not name resolution)
323+
UnknownHostException unknownHostException = new UnknownHostException("No route to host");
324+
ApiException apiException = new ApiException(unknownHostException);
325+
326+
executeTestWithException(apiException);
327+
}
328+
329+
@Test
330+
void withGeneralExceptionNameResolution() throws ApiException {
331+
// Create a general Exception (not ApiException) with name resolution error
332+
RuntimeException generalException = new RuntimeException("Connection failed: Name does not resolve");
333+
334+
executeTestWithException(generalException);
335+
}
336+
337+
private void executeTestWithException(Exception exception) throws ApiException {
338+
ClientProfileApi clientProfileApi = Mockito.mock(ClientProfileApi.class);
339+
when(sempApiProvider.getClientProfileApi()).thenReturn(clientProfileApi);
340+
341+
when(clientProfileApi.getMsgVpnClientProfile(any(), any(), any(), any()))
342+
.thenThrow(exception);
343+
344+
Command cmd = Command.builder()
345+
.commandType(CommandType.semp)
346+
.command(SEMP_GET_OPERATION)
347+
.parameters(createClientProfileGetParameters(true))
348+
.build();
349+
350+
sempGetCommandManager.execute(cmd, sempApiProvider);
351+
352+
verify(clientProfileApi).getMsgVpnClientProfile(eq("default"), eq("testClientProfile"), any(), any());
353+
assertThat(cmd.getResult().getStatus()).isEqualTo(JobStatus.error);
354+
}
355+
247356
private Map<String, Object> createClientProfileGetParameters(boolean valid) {
248357
Map<String, Object> parameters = new HashMap<>();
249358
parameters.put(SEMP_COMMAND_ENTITY_TYPE, solaceClientProfileName.name());
@@ -272,4 +381,4 @@ private String readSempResponseResource(String resourceName) {
272381
throw new RuntimeException(e);
273382
}
274383
}
275-
}
384+
}

service/plugin/src/main/java/com/solace/maas/ep/event/management/agent/plugin/route/exceptionhandlers/GeneralExceptionHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class GeneralExceptionHandler implements Processor {
1616
public void process(Exchange exchange) throws Exception {
1717
Exception cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
1818

19-
log.error("Oops! Something went wrong: {}", cause.toString());
19+
log.warn("Oops! Something went wrong: {}", cause.toString());
2020

2121
exchange.getIn().setHeader(ErrorConstants.GENERAL_ERROR, constant(true));
2222
exchange.getIn().setHeader(RouteConstants.SCAN_STATUS, ScanStatus.FAILED);

service/solace-plugin/src/main/java/com/solace/maas/ep/event/management/agent/plugin/solace/processor/semp/SolaceHttpSemp.java

Lines changed: 98 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
@Slf4j
3838
@Getter
3939
public class SolaceHttpSemp {
40+
private static final String FAILED_TO_RESOLVE = "Failed to resolve";
41+
private static final String ERROR_CONNECTING_HOSTNAME = "Error connecting to messaging service. Check that the hostname is correct.";
42+
private static final String ERROR_CONNECTING_PORT = "Error connecting to messaging service. Check that the port is correct";
43+
private static final String ERROR_CONNECTING_SSL = "Error connecting to messaging service. SSL certificate validation failed.";
4044
private final static String GET_SYSTEM_INFORMATION = "/SEMP/v2/config/about/api";
4145
private final static String GET_QUEUES_URI = "/SEMP/v2/config/msgVpns/{msgvpn}/queues";
4246
private final static String GET_TOPIC_SUBSCRIPTIONS_FOR_QUEUE_URI = "/SEMP/v2/config/msgVpns/{msgvpn}/queues/{queuename}/subscriptions";
@@ -115,11 +119,13 @@ private List<Map<String, Object>> getResultsListMapFromSemp(String uriPath,
115119
log.error("Error during SEMP Data Collection. The format of the collected data is unexpected.", ioException);
116120
throw new PluginClientException(ioException);
117121
} catch (WebClientRequestException requestException) {
118-
if (requestException.getMessage().startsWith("Failed to resolve")) {
119-
log.error("Error connecting to messaging service. Check that the hostname is correct.", requestException);
120-
throw new PluginClientException(requestException);
122+
if (requestException.getMessage().startsWith(FAILED_TO_RESOLVE)) {
123+
log.warn(ERROR_CONNECTING_HOSTNAME, requestException);
124+
} else if (isSslCertificateError(requestException)) {
125+
log.warn(ERROR_CONNECTING_SSL, requestException);
126+
} else {
127+
log.warn(ERROR_CONNECTING_PORT, requestException);
121128
}
122-
log.error("Error connecting to messaging service. Check that the port is correct", requestException);
123129
throw new PluginClientException(requestException);
124130
}
125131
return sempObject;
@@ -198,40 +204,99 @@ private void handlePagedSempResponse(List<Map<String, Object>> list,
198204

199205
private SempListResponse<Map<String, Object>> getSempListResponse(Function<UriBuilder, URI> uriMethod) throws
200206
com.fasterxml.jackson.core.JsonProcessingException {
201-
String rawResponse = sempClient.getWebClient()
202-
.get()
203-
.uri(uriMethod)
204-
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
205-
.header(HttpHeaders.AUTHORIZATION, buildBasicAuthorization())
206-
.accept(MediaType.APPLICATION_JSON)
207-
.retrieve()
208-
.bodyToMono(String.class)
209-
.block();
207+
try {
208+
String rawResponse = sempClient.getWebClient()
209+
.get()
210+
.uri(uriMethod)
211+
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
212+
.header(HttpHeaders.AUTHORIZATION, buildBasicAuthorization())
213+
.accept(MediaType.APPLICATION_JSON)
214+
.retrieve()
215+
.bodyToMono(String.class)
216+
.block();
210217

211-
return objectMapper.readValue(rawResponse,
212-
new TypeReference<>() {
213-
});
218+
return objectMapper.readValue(rawResponse,
219+
new TypeReference<>() {
220+
});
221+
} catch (WebClientRequestException requestException) {
222+
if (requestException.getMessage().contains(FAILED_TO_RESOLVE)) {
223+
log.warn(ERROR_CONNECTING_HOSTNAME, requestException);
224+
} else if (isSslCertificateError(requestException)) {
225+
log.warn(ERROR_CONNECTING_SSL, requestException);
226+
} else {
227+
log.warn(ERROR_CONNECTING_PORT, requestException);
228+
}
229+
throw requestException;
230+
}
214231
}
215232

216233
private Map<String, Object> getSempFlatRequest(Function<UriBuilder, URI> uriMethod) throws IOException {
217-
String rawResponse = sempClient.getWebClient()
218-
.get()
219-
.uri(uriMethod)
220-
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
221-
.header(HttpHeaders.AUTHORIZATION, buildBasicAuthorization())
222-
.accept(MediaType.APPLICATION_JSON)
223-
.retrieve()
224-
.bodyToMono(String.class)
225-
.block();
226-
227-
SempFlatResponse<Map<String, Object>> sempFlatResponse = objectMapper.readValue(rawResponse,
228-
new TypeReference<>() {
229-
});
230-
231-
if (sempFlatResponse != null) {
232-
return sempFlatResponse.getData();
234+
try {
235+
String rawResponse = sempClient.getWebClient()
236+
.get()
237+
.uri(uriMethod)
238+
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
239+
.header(HttpHeaders.AUTHORIZATION, buildBasicAuthorization())
240+
.accept(MediaType.APPLICATION_JSON)
241+
.retrieve()
242+
.bodyToMono(String.class)
243+
.block();
244+
245+
SempFlatResponse<Map<String, Object>> sempFlatResponse = objectMapper.readValue(rawResponse,
246+
new TypeReference<>() {
247+
});
248+
249+
if (sempFlatResponse != null) {
250+
return sempFlatResponse.getData();
251+
}
252+
return null;
253+
} catch (WebClientRequestException requestException) {
254+
if (requestException.getMessage().contains(FAILED_TO_RESOLVE)) {
255+
log.warn(ERROR_CONNECTING_HOSTNAME, requestException);
256+
} else if (isSslCertificateError(requestException)) {
257+
log.warn(ERROR_CONNECTING_SSL, requestException);
258+
} else {
259+
log.warn(ERROR_CONNECTING_PORT, requestException);
260+
}
261+
throw requestException;
262+
}
263+
}
264+
265+
/**
266+
* Checks if the exception represents an SSL certificate validation error.
267+
* This method examines the exception message and cause chain for SSL/TLS certificate-related errors.
268+
*/
269+
public boolean isSslCertificateError(WebClientRequestException requestException) {
270+
// Check the exception message for SSL certificate indicators
271+
String message = requestException.getMessage();
272+
if (message != null && (message.contains("PKIX path building failed") ||
273+
message.contains("unable to find valid certification path") ||
274+
message.contains("certificate validation failed") ||
275+
message.contains("SSLHandshakeException"))) {
276+
return true;
233277
}
234-
return null;
278+
279+
// Check the cause chain for SSL-related exceptions
280+
Throwable cause = requestException.getCause();
281+
while (cause != null) {
282+
String causeMessage = cause.getMessage();
283+
if (causeMessage != null && (causeMessage.contains("PKIX path building failed") ||
284+
causeMessage.contains("unable to find valid certification path") ||
285+
causeMessage.contains("certificate validation failed"))) {
286+
return true;
287+
}
288+
289+
// Check for specific SSL exception types
290+
if (cause instanceof javax.net.ssl.SSLHandshakeException ||
291+
cause instanceof java.security.cert.CertPathBuilderException ||
292+
cause.getClass().getName().contains("CertPathBuilderException")) {
293+
return true;
294+
}
295+
296+
cause = cause.getCause();
297+
}
298+
299+
return false;
235300
}
236301

237302
private String buildBasicAuthorization() {

0 commit comments

Comments
 (0)