Skip to content

Commit 1a050db

Browse files
authored
Merge pull request #255 from SolaceProducts/boliao/DATAGO-101453-client-profile-preflight-validation
DATAGO-101453: add SEMP GET command and Client profile validation
2 parents 7355005 + b03917f commit 1a050db

File tree

11 files changed

+493
-16
lines changed

11 files changed

+493
-16
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.solace.maas.ep.common.model;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
6+
@Data
7+
@Builder
8+
public class SempClientProfileValidationRequest {
9+
private String msgVpn;
10+
private String clientProfileName;
11+
}

service/application/src/main/java/com/solace/maas/ep/common/model/SempEntityType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public enum SempEntityType {
1313
solaceAclPublishTopicException("solaceAclPublishTopicException"),
1414
solaceClientUsername("solaceClientUsername"),
1515
solaceClientCertificateUsername("solaceClientCertificateUsername"),
16+
solaceClientProfileName("solaceClientProfileName"),
1617
solaceAuthorizationGroup("solaceAuthorizationGroup"),
1718
solaceQueueSubscriptionTopic("solaceQueueSubscriptionTopic");
1819
private final String value;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void execute(Command command, SempApiProvider sempApiProvider) {
4040
protected abstract void executeSempCommand(Command command, SempApiProvider sempApiProvider) throws Exception;
4141

4242

43-
private void validate(Command command, SempApiProvider sempApiProvider) {
43+
public void validate(Command command, SempApiProvider sempApiProvider) {
4444
Validate.isTrue(command.getCommand().equals(supportedSempCommand()), "Command must be " + supportedSempCommand());
4545
Validate.isTrue(command.getCommandType().equals(CommandType.semp), "Command type must be semp");
4646
Validate.notNull(sempApiProvider, "SempApiProvider must not be null");

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import static com.solace.maas.ep.event.management.agent.plugin.command.model.CommandType.semp;
4141
import static com.solace.maas.ep.event.management.agent.plugin.command.model.CommandType.terraform;
4242
import static com.solace.maas.ep.event.management.agent.plugin.command.model.SempCommandConstants.SEMP_DELETE_OPERATION;
43+
import static com.solace.maas.ep.event.management.agent.plugin.command.model.SempCommandConstants.SEMP_GET_OPERATION;
4344
import static com.solace.maas.ep.event.management.agent.plugin.command.model.SempCommandConstants.SEMP_PATCH_OPERATION;
4445
import static com.solace.maas.ep.event.management.agent.plugin.constants.RouteConstants.ACTOR_ID;
4546
import static com.solace.maas.ep.event.management.agent.plugin.constants.RouteConstants.TRACE_ID;
@@ -60,6 +61,7 @@ public class CommandManager {
6061
private final MeterRegistry meterRegistry;
6162
private final SempDeleteCommandManager sempDeleteCommandManager;
6263
private final SempPatchCommandManager sempPatchCommandManager;
64+
private final SempGetCommandManager sempGetCommandManager;
6365
private final TerraformLogProcessingService terraformLoggingService;
6466

6567
public CommandManager(TerraformManager terraformManager,
@@ -71,7 +73,8 @@ public CommandManager(TerraformManager terraformManager,
7173
MeterRegistry meterRegistry,
7274
final SempDeleteCommandManager sempDeleteCommandManager,
7375
TerraformLogProcessingService terraformLoggingService,
74-
SempPatchCommandManager sempPatchCommandManager) {
76+
SempPatchCommandManager sempPatchCommandManager,
77+
SempGetCommandManager sempGetCommandManager) {
7578
this.terraformManager = terraformManager;
7679
this.commandMapper = commandMapper;
7780
this.commandPublisher = commandPublisher;
@@ -83,6 +86,7 @@ public CommandManager(TerraformManager terraformManager,
8386
this.sempDeleteCommandManager = sempDeleteCommandManager;
8487
this.terraformLoggingService = terraformLoggingService;
8588
this.sempPatchCommandManager = sempPatchCommandManager;
89+
this.sempGetCommandManager = sempGetCommandManager;
8690
}
8791

8892
public void execute(CommandMessage request) {
@@ -131,6 +135,7 @@ private void configPush(CommandRequest request) {
131135

132136
for (CommandBundle bundle : request.getCommandBundles()) {
133137
boolean exitEarlyOnFailedCommand = bundle.getExitOnFailure();
138+
134139
// For now everything is run serially
135140
for (Command command : bundle.getCommands()) {
136141
attachErrorToTerraformCommand = false;
@@ -244,19 +249,21 @@ private Path executeTerraformCommand(CommandRequest request, Command command, Ma
244249
private void executeSempCommand(Command command, SolaceHttpSemp solaceClient) {
245250
try {
246251
Validate.isTrue(command.getCommandType().equals(semp), "Command type must be semp");
247-
// only delete operation is supported for now and only delete operations are sent from EP
248252
Validate.isTrue(command.getCommand().equals(SEMP_DELETE_OPERATION)
249-
|| command.getCommand().equals(SEMP_PATCH_OPERATION),
250-
"Command operation must be delete or patch");
253+
|| command.getCommand().equals(SEMP_PATCH_OPERATION)
254+
|| command.getCommand().equals(SEMP_GET_OPERATION),
255+
"Command operation must be delete, patch or get");
251256

252257
// creating a new SempApiProviderImpl instance for each command execution
253258
// if this becomes a performance issue, we can consider caching the SempApiProviderImpl instance for each serviceId
259+
SempApiProviderImpl sempApiProvider = new SempApiProviderImpl(solaceClient, eventPortalProperties);
260+
254261
if (command.getCommand().equals(SEMP_PATCH_OPERATION)) {
255-
sempPatchCommandManager.execute(command, new SempApiProviderImpl(solaceClient, eventPortalProperties));
262+
sempPatchCommandManager.execute(command, sempApiProvider);
256263
} else if (command.getCommand().equals(SEMP_DELETE_OPERATION)) {
257-
sempDeleteCommandManager.execute(command, new SempApiProviderImpl(solaceClient, eventPortalProperties));
264+
sempDeleteCommandManager.execute(command, sempApiProvider);
258265
} else {
259-
throw new UnsupportedOperationException("Unsupported SEMP command operation: " + command.getCommand());
266+
sempGetCommandManager.execute(command, sempApiProvider);
260267
}
261268
} catch (Exception e) {
262269
log.error(ERROR_EXECUTING_COMMAND, e);
@@ -317,10 +324,12 @@ public void streamCommandExecutionLogToEpCore(CommandRequest request, Command co
317324
}
318325

319326

320-
private static boolean exitEarlyOnFailedCommand(boolean existEarlyOnFailedCommand, Command command) {
321-
return Boolean.TRUE.equals(existEarlyOnFailedCommand)
327+
private static boolean exitEarlyOnFailedCommand(boolean exitEarlyOnFailedCommand, Command command) {
328+
return Boolean.TRUE.equals(exitEarlyOnFailedCommand)
322329
&& Boolean.FALSE.equals(command.getIgnoreResult())
323-
&& (command.getResult() == null || JobStatus.error.equals(command.getResult().getStatus()));
330+
&& (command.getResult() == null
331+
|| command.getResult().getStatus() == JobStatus.error
332+
|| command.getResult().getStatus() == JobStatus.validation_error);
324333
}
325334

326335

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package com.solace.maas.ep.event.management.agent.command;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.solace.client.sempv2.ApiException;
6+
import com.solace.client.sempv2.api.ClientProfileApi;
7+
import com.solace.maas.ep.common.model.SempClientProfileValidationRequest;
8+
import com.solace.maas.ep.common.model.SempEntityType;
9+
import com.solace.maas.ep.event.management.agent.command.semp.SempApiProvider;
10+
import com.solace.maas.ep.event.management.agent.plugin.command.model.Command;
11+
import com.solace.maas.ep.event.management.agent.plugin.command.model.CommandResult;
12+
import com.solace.maas.ep.event.management.agent.plugin.command.model.JobStatus;
13+
import com.solace.maas.ep.event.management.agent.plugin.command.model.SempCommandConstants;
14+
import lombok.extern.slf4j.Slf4j;
15+
import org.apache.commons.lang3.StringUtils;
16+
import org.apache.commons.lang3.Validate;
17+
import org.springframework.stereotype.Service;
18+
19+
import java.time.OffsetDateTime;
20+
import java.util.HashMap;
21+
import java.util.List;
22+
import java.util.Map;
23+
24+
import static com.solace.maas.ep.event.management.agent.plugin.terraform.manager.TerraformUtils.setCommandError;
25+
26+
@Service
27+
@Slf4j
28+
public class SempGetCommandManager extends AbstractSempCommandManager {
29+
private final ObjectMapper objectMapper;
30+
31+
public SempGetCommandManager(ObjectMapper objectMapper) {
32+
super();
33+
this.objectMapper = objectMapper;
34+
}
35+
36+
@Override
37+
public String supportedSempCommand() {
38+
return SempCommandConstants.SEMP_GET_OPERATION;
39+
}
40+
41+
/**
42+
* Executes a SEMP GET command against the broker.
43+
* Specifically handles client profile validation by checking if a profile exists.
44+
* Sets appropriate status and error messages based on the result.
45+
*
46+
* @param command The command to execute
47+
* @param sempApiProvider The provider for SEMP APIs
48+
*/
49+
@Override
50+
public void execute(Command command, SempApiProvider sempApiProvider) {
51+
try {
52+
validate(command, sempApiProvider);
53+
executeSempCommand(command, sempApiProvider);
54+
command.setResult(CommandResult.builder()
55+
.status(JobStatus.success)
56+
.logs(List.of(Map.of(
57+
"message", "Resource found",
58+
"level", "INFO",
59+
"timestamp", OffsetDateTime.now()
60+
)))
61+
.build());
62+
} catch (ApiException e) {
63+
String entityType = (String) command.getParameters().get(SempCommandConstants.SEMP_COMMAND_ENTITY_TYPE);
64+
if (SempEntityType.solaceClientProfileName.name().equals(entityType) &&
65+
isResourceNotFoundError(e)) {
66+
handleClientProfileNotFound(command);
67+
} else {
68+
log.error("SEMP {} command not executed successfully", supportedSempCommand(), e);
69+
setCommandError(command, e);
70+
}
71+
} catch (Exception e) {
72+
log.error("SEMP {} command not executed successfully", supportedSempCommand(), e);
73+
setCommandError(command, e);
74+
}
75+
}
76+
77+
private boolean isResourceNotFoundError(ApiException e) {
78+
return e.getCode() == 404 ||
79+
(e.getCode() == 400 && StringUtils.contains(e.getResponseBody(), "NOT_FOUND"));
80+
}
81+
82+
private void handleClientProfileNotFound(Command command) {
83+
String resourceName = extractClientProfileName(command);
84+
String errorMessage = String.format("Named client profile not found on the event broker: %s", resourceName);
85+
Map<String, Object> resultMap = new HashMap<>();
86+
resultMap.put(SempCommandConstants.VALIDATION_ERROR_MESSAGE, errorMessage);
87+
log.warn(errorMessage);
88+
89+
command.setIgnoreResult(false);
90+
command.setResult(CommandResult.builder()
91+
.status(JobStatus.validation_error)
92+
.result(resultMap)
93+
.logs(List.of(Map.of(
94+
"message", errorMessage,
95+
"level", "WARN",
96+
"timestamp", OffsetDateTime.now()
97+
)))
98+
.build());
99+
}
100+
101+
private String extractClientProfileName(Command command) {
102+
try {
103+
Object data = command.getParameters().get(SempCommandConstants.SEMP_COMMAND_DATA);
104+
if (data != null) {
105+
SempClientProfileValidationRequest request = objectMapper.readValue(
106+
objectMapper.writeValueAsString(data),
107+
SempClientProfileValidationRequest.class);
108+
return request.getClientProfileName();
109+
}
110+
} catch (Exception e) {
111+
log.warn("Failed to extract resource name from SEMP {} command", supportedSempCommand(), e);
112+
}
113+
return "unknown";
114+
}
115+
116+
@Override
117+
protected void executeSempCommand(Command command, SempApiProvider sempApiProvider) throws Exception {
118+
String entityType = (String) command.getParameters().get(SempCommandConstants.SEMP_COMMAND_ENTITY_TYPE);
119+
SempEntityType getEntityType;
120+
121+
if (entityType == null) {
122+
throw new IllegalArgumentException("Entity type of a SEMP get command must not be null");
123+
}
124+
125+
try {
126+
getEntityType = SempEntityType.valueOf(entityType);
127+
} catch (IllegalArgumentException e) {
128+
throw new IllegalArgumentException("Unsupported SEMP get entity type: " + entityType);
129+
}
130+
131+
switch (getEntityType) {
132+
case solaceClientProfileName:
133+
executeGetClientProfile(command, sempApiProvider);
134+
break;
135+
default:
136+
throw new UnsupportedOperationException("Unsupported entity type for get: " + getEntityType);
137+
}
138+
}
139+
140+
private void executeGetClientProfile(Command command, SempApiProvider sempApiProvider) throws ApiException, JsonProcessingException {
141+
ClientProfileApi clientProfileApi = sempApiProvider.getClientProfileApi();
142+
143+
SempClientProfileValidationRequest request = objectMapper.readValue(
144+
objectMapper.writeValueAsString(command.getParameters().get(SempCommandConstants.SEMP_COMMAND_DATA)),
145+
SempClientProfileValidationRequest.class);
146+
147+
Validate.notEmpty(request.getMsgVpn(), MSG_VPN_EMPTY_ERROR_MSG);
148+
Validate.notEmpty(request.getClientProfileName(), "Client profile name must not be empty");
149+
150+
log.debug("SEMP {} command: Checking client profile name existence", supportedSempCommand());
151+
152+
// This will throw ApiException if the client profile doesn't exist
153+
clientProfileApi.getMsgVpnClientProfile(
154+
request.getMsgVpn(),
155+
request.getClientProfileName(),
156+
null, // opaquePassword
157+
null // select
158+
);
159+
}
160+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@
22

33
import com.solace.client.sempv2.api.AclProfileApi;
44
import com.solace.client.sempv2.api.AuthorizationGroupApi;
5+
import com.solace.client.sempv2.api.ClientProfileApi;
56
import com.solace.client.sempv2.api.ClientUsernameApi;
67
import com.solace.client.sempv2.api.QueueApi;
78
import com.solace.client.sempv2.api.RestDeliveryPointApi;
89

910
public interface SempApiProvider {
1011

1112
AclProfileApi getAclProfileApi();
13+
1214
AuthorizationGroupApi getAuthorizationGroupApi();
15+
1316
ClientUsernameApi getClientUsernameApi();
17+
1418
QueueApi getQueueApi();
19+
1520
RestDeliveryPointApi getRestDeliveryPointApi();
1621

22+
ClientProfileApi getClientProfileApi();
23+
1724
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.solace.client.sempv2.ApiClient;
44
import com.solace.client.sempv2.api.AclProfileApi;
55
import com.solace.client.sempv2.api.AuthorizationGroupApi;
6+
import com.solace.client.sempv2.api.ClientProfileApi;
67
import com.solace.client.sempv2.api.ClientUsernameApi;
78
import com.solace.client.sempv2.api.QueueApi;
89
import com.solace.client.sempv2.api.RestDeliveryPointApi;
@@ -21,10 +22,11 @@ public class SempApiProviderImpl implements SempApiProvider {
2122
private ClientUsernameApi clientUsernameApi;
2223
private QueueApi queueApi;
2324
private RestDeliveryPointApi restDeliveryPointApi;
25+
private ClientProfileApi clientProfileApi;
2426

2527
public SempApiProviderImpl(SolaceHttpSemp solaceClient,
2628
EventPortalProperties eventPortalProperties) {
27-
this.apiClient = setupApiClient(solaceClient, eventPortalProperties);
29+
apiClient = setupApiClient(solaceClient, eventPortalProperties);
2830
}
2931

3032
@Override
@@ -59,6 +61,14 @@ public QueueApi getQueueApi() {
5961
return queueApi;
6062
}
6163

64+
@Override
65+
public ClientProfileApi getClientProfileApi() {
66+
if (clientProfileApi == null) {
67+
clientProfileApi = new ClientProfileApi(apiClient);
68+
}
69+
return clientProfileApi;
70+
}
71+
6272
private ApiClient setupApiClient(SolaceHttpSemp solaceClient, EventPortalProperties eventPortalProperties) {
6373
SempClient sempClient = solaceClient.getSempClient();
6474
ApiClient client = new ApiClient();

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.solace.maas.ep.event.management.agent.command.CommandManager;
44
import com.solace.maas.ep.event.management.agent.command.SempDeleteCommandManager;
5+
import com.solace.maas.ep.event.management.agent.command.SempGetCommandManager;
56
import com.solace.maas.ep.event.management.agent.command.SempPatchCommandManager;
67
import com.solace.maas.ep.event.management.agent.command.mapper.CommandMapper;
78
import com.solace.maas.ep.event.management.agent.config.SolaceConfiguration;
@@ -175,7 +176,8 @@ public CommandManager getCommandManager(TerraformManager terraformManager,
175176
MeterRegistry meterRegistry,
176177
SempDeleteCommandManager sempDeleteCommandManager,
177178
TerraformLogProcessingService terraformLogProcessingService,
178-
SempPatchCommandManager sempPatchCommandManager) {
179+
SempPatchCommandManager sempPatchCommandManager,
180+
SempGetCommandManager sempGetCommandManager) {
179181
return new CommandManager(
180182
terraformManager,
181183
commandMapper,
@@ -186,8 +188,8 @@ public CommandManager getCommandManager(TerraformManager terraformManager,
186188
meterRegistry,
187189
sempDeleteCommandManager,
188190
terraformLogProcessingService,
189-
sempPatchCommandManager
190-
191+
sempPatchCommandManager,
192+
sempGetCommandManager
191193
);
192194
}
193195

0 commit comments

Comments
 (0)