Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit 80daedd

Browse files
committed
DEVEXP-545 Can now test connections
1 parent 8f898d5 commit 80daedd

File tree

7 files changed

+562
-5
lines changed

7 files changed

+562
-5
lines changed

src/main/java/com/marklogic/appdeployer/command/CommandContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ public Map<String, Object> getContextMap() {
120120
return contextMap;
121121
}
122122

123+
/**
124+
* @param contextMap
125+
* @deprecated since 4.6.0, will be removed in 5.0.0; the contextMap is not intended to be replaced.
126+
*/
127+
@Deprecated
123128
public void setContextMap(Map<String, Object> contextMap) {
124129
this.contextMap = contextMap;
125130
}
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
package com.marklogic.appdeployer.command;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.node.ArrayNode;
5+
import com.marklogic.appdeployer.AppConfig;
6+
import com.marklogic.client.DatabaseClient;
7+
import com.marklogic.client.ext.SecurityContextType;
8+
import com.marklogic.mgmt.ManageClient;
9+
import com.marklogic.mgmt.admin.AdminManager;
10+
import com.marklogic.mgmt.cma.ConfigurationManager;
11+
import com.marklogic.mgmt.resource.clusters.ClusterManager;
12+
import com.marklogic.rest.util.RestConfig;
13+
import org.springframework.web.client.DefaultResponseErrorHandler;
14+
import org.springframework.web.client.HttpClientErrorException;
15+
import org.springframework.web.client.ResponseErrorHandler;
16+
17+
import javax.net.ssl.SSLContext;
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.function.Supplier;
21+
import java.util.stream.Stream;
22+
23+
/**
24+
* Command for testing each of the connections that can be made to MarkLogic based on the configuration in a
25+
* {@code CommandContext}.
26+
*
27+
* @since 4.6.0
28+
*/
29+
public class TestConnectionsCommand extends AbstractCommand {
30+
31+
/**
32+
* If run in a deployment process, this should run immediately so as to fail fast.
33+
*/
34+
public TestConnectionsCommand() {
35+
setExecuteSortOrder(0);
36+
}
37+
38+
/**
39+
* Can be included in a deployment process so that the deployment fails if any of the connections fail.
40+
*
41+
* @param context
42+
*/
43+
@Override
44+
public void execute(CommandContext context) {
45+
TestResults results = testConnections(context);
46+
if (results.anyTestFailed()) {
47+
throw new RuntimeException(results.toString());
48+
}
49+
logger.info(results.toString());
50+
}
51+
52+
/**
53+
* Intended for execution outside a deployment process, where the client wants access to the test results and
54+
* will choose how to present those to a user.
55+
*
56+
* @param context
57+
* @return
58+
*/
59+
public TestResults testConnections(CommandContext context) {
60+
try {
61+
TestResult manageResult = testManageAppServer(context.getManageClient());
62+
TestResult adminResult = testAdminAppServer(context.getAdminManager());
63+
64+
TestResult appServicesResult = null;
65+
TestResult restResult = null;
66+
TestResult testRestResult = null;
67+
if (manageResult.isSucceeded()) {
68+
List<Integer> serverPorts = getAppServerPorts(context.getManageClient());
69+
appServicesResult = testAppServicesAppServer(context.getAppConfig(), serverPorts);
70+
restResult = testRestAppServer(context.getAppConfig(), serverPorts);
71+
testRestResult = testTestRestAppServer(context.getAppConfig(), serverPorts);
72+
}
73+
74+
return new TestResults(manageResult, adminResult, appServicesResult, restResult, testRestResult);
75+
} catch (Exception ex) {
76+
// We don't expect any exceptions above, as each connection test has its own try/catch block.
77+
// This is simply to pretty up the error a bit.
78+
throw new RuntimeException("Unable to test connections; cause: " + ex.getMessage(), ex);
79+
}
80+
}
81+
82+
private List<Integer> getAppServerPorts(ManageClient manageClient) {
83+
JsonNode json = new ConfigurationManager(manageClient).getResourcesAsJson("server").getBody();
84+
ArrayNode servers = (ArrayNode) json.get("config").get(0).get("server");
85+
List<Integer> ports = new ArrayList<>();
86+
servers.forEach(server -> {
87+
if (server.has("port")) {
88+
ports.add(server.get("port").asInt());
89+
}
90+
});
91+
return ports;
92+
}
93+
94+
private TestResult testAppServicesAppServer(AppConfig appConfig, List<Integer> serverPorts) {
95+
if (appConfig.getAppServicesPort() != null && serverPorts.contains(appConfig.getAppServicesPort())) {
96+
return testWithDatabaseClient(appConfig.getHost(), appConfig.getAppServicesPort(),
97+
appConfig.getAppServicesSslContext(), appConfig.getAppServicesSecurityContextType(),
98+
appConfig.getAppServicesUsername(), () -> appConfig.newAppServicesDatabaseClient(null));
99+
}
100+
return null;
101+
}
102+
103+
private TestResult testRestAppServer(AppConfig appConfig, List<Integer> serverPorts) {
104+
if (appConfig.getRestPort() != null && serverPorts.contains(appConfig.getRestPort())) {
105+
return testWithDatabaseClient(appConfig.getHost(), appConfig.getRestPort(),
106+
appConfig.getRestSslContext(), appConfig.getRestSecurityContextType(),
107+
appConfig.getRestAdminUsername(), appConfig::newDatabaseClient);
108+
}
109+
return null;
110+
}
111+
112+
private TestResult testTestRestAppServer(AppConfig appConfig, List<Integer> serverPorts) {
113+
if (appConfig.getTestRestPort() != null && serverPorts.contains(appConfig.getTestRestPort())) {
114+
return testWithDatabaseClient(appConfig.getHost(), appConfig.getTestRestPort(),
115+
appConfig.getRestSslContext(), appConfig.getRestSecurityContextType(),
116+
appConfig.getRestAdminUsername(), appConfig::newTestDatabaseClient);
117+
}
118+
return null;
119+
}
120+
121+
public static class TestResults {
122+
private TestResult manageTestResult;
123+
private TestResult adminTestResult;
124+
private TestResult appServicesTestResult;
125+
private TestResult restServerTestResult;
126+
private TestResult testRestServerTestResult;
127+
128+
public TestResults(TestResult manageTestResult, TestResult adminTestResult,
129+
TestResult appServicesTestResult, TestResult restServerTestResult, TestResult testRestServerTestResult) {
130+
this.manageTestResult = manageTestResult;
131+
this.adminTestResult = adminTestResult;
132+
this.appServicesTestResult = appServicesTestResult;
133+
this.restServerTestResult = restServerTestResult;
134+
this.testRestServerTestResult = testRestServerTestResult;
135+
}
136+
137+
public boolean anyTestFailed() {
138+
return Stream.of(manageTestResult, adminTestResult, appServicesTestResult, restServerTestResult, testRestServerTestResult)
139+
.anyMatch(test -> test != null && !test.isSucceeded());
140+
}
141+
142+
public TestResult getManageTestResult() {
143+
return manageTestResult;
144+
}
145+
146+
public TestResult getAdminTestResult() {
147+
return adminTestResult;
148+
}
149+
150+
public TestResult getAppServicesTestResult() {
151+
return appServicesTestResult;
152+
}
153+
154+
public TestResult getRestServerTestResult() {
155+
return restServerTestResult;
156+
}
157+
158+
public TestResult getTestRestServerTestResult() {
159+
return testRestServerTestResult;
160+
}
161+
162+
/**
163+
* @return a multi-line summary of all the non-null test results
164+
*/
165+
@Override
166+
public String toString() {
167+
StringBuilder sb = new StringBuilder();
168+
sb.append("Manage App Server\n").append(getManageTestResult())
169+
.append("\n\nAdmin App Server\n").append(getAdminTestResult());
170+
if (getManageTestResult().isSucceeded()) {
171+
if (getAppServicesTestResult() != null) {
172+
sb.append("\n\nApp-Services App Server\n").append(getAppServicesTestResult());
173+
}
174+
if (getRestServerTestResult() != null) {
175+
sb.append("\n\nREST API App Server\n").append(getRestServerTestResult());
176+
}
177+
if (getTestRestServerTestResult() != null) {
178+
sb.append("\n\nTest REST API App Server\n").append(getTestRestServerTestResult());
179+
}
180+
} else {
181+
sb.append("\n\nCould not test connections against the App-Services or REST API App Servers " +
182+
"due to the Manage App Server connection failing.");
183+
}
184+
return sb.toString();
185+
}
186+
}
187+
188+
public static class TestResult {
189+
private String host;
190+
private int port;
191+
private String scheme;
192+
private String authType;
193+
private String username;
194+
private boolean succeeded;
195+
private String message;
196+
197+
public TestResult(RestConfig restConfig, boolean succeeded, String message) {
198+
this(restConfig.getHost(), restConfig.getPort(), restConfig.getScheme(), restConfig.getAuthType(),
199+
restConfig.getUsername(), succeeded, message);
200+
}
201+
202+
public TestResult(RestConfig restConfig, Exception ex) {
203+
this(restConfig, false, ex.getMessage());
204+
}
205+
206+
public TestResult(String host, int port, String scheme, String authType, String username, DatabaseClient.ConnectionResult result) {
207+
this.host = host;
208+
this.port = port;
209+
this.scheme = scheme;
210+
this.authType = authType;
211+
this.username = username;
212+
this.succeeded = result.isConnected();
213+
if (!result.isConnected()) {
214+
this.message = String.format("Received %d: %s", result.getStatusCode(), result.getErrorMessage());
215+
}
216+
}
217+
218+
public TestResult(String host, int port, String scheme, String authType, String username, boolean succeeded, String message) {
219+
this.host = host;
220+
this.port = port;
221+
this.scheme = scheme;
222+
this.authType = authType;
223+
this.username = username;
224+
this.succeeded = succeeded;
225+
this.message = message;
226+
}
227+
228+
public String getHost() {
229+
return host;
230+
}
231+
232+
public int getPort() {
233+
return port;
234+
}
235+
236+
public String getScheme() {
237+
return scheme;
238+
}
239+
240+
public boolean isSucceeded() {
241+
return succeeded;
242+
}
243+
244+
public String getMessage() {
245+
return message;
246+
}
247+
248+
public String getAuthType() {
249+
return authType;
250+
}
251+
252+
public String getUsername() {
253+
return username;
254+
}
255+
256+
/**
257+
* @return a multi-line representation of the test result
258+
*/
259+
@Override
260+
public String toString() {
261+
String result = String.format("Configured to connect to %s://%s:%d using '%s' authentication",
262+
getScheme(), getHost(), getPort(), getAuthType());
263+
if (getUsername() != null) {
264+
result += String.format(" and username of '%s'", getUsername());
265+
}
266+
if (isSucceeded()) {
267+
result += "\nConnected successfully";
268+
return getMessage() != null ? result + "; " + getMessage() : result;
269+
}
270+
return result + "\nFAILED TO CONNECT; cause: " + message;
271+
}
272+
}
273+
274+
private TestResult testManageAppServer(ManageClient client) {
275+
ResponseErrorHandler originalErrorHandler = client.getRestTemplate().getErrorHandler();
276+
client.getRestTemplate().setErrorHandler(new DefaultResponseErrorHandler());
277+
try {
278+
String version = new ClusterManager(client).getVersion();
279+
return new TestResult(client.getManageConfig(), true, "MarkLogic version: " + version);
280+
} catch (Exception ex) {
281+
if (ex instanceof HttpClientErrorException && ((HttpClientErrorException) ex).getRawStatusCode() == 404) {
282+
return new TestResult(client.getManageConfig(), false,
283+
"Unable to access /manage/v2; received 404; unexpected response: " + ex.getMessage());
284+
} else {
285+
return new TestResult(client.getManageConfig(), ex);
286+
}
287+
} finally {
288+
client.getRestTemplate().setErrorHandler(originalErrorHandler);
289+
}
290+
}
291+
292+
private TestResult testAdminAppServer(AdminManager adminManager) {
293+
try {
294+
String timestamp = adminManager.getServerTimestamp();
295+
return new TestResult(adminManager.getAdminConfig(), true, "MarkLogic server timestamp: " + timestamp);
296+
} catch (Exception ex) {
297+
return new TestResult(adminManager.getAdminConfig(), ex);
298+
}
299+
}
300+
301+
private TestResult testWithDatabaseClient(String host, Integer port, SSLContext sslContext,
302+
SecurityContextType securityContextType, String username, Supplier<DatabaseClient> supplier) {
303+
if (port == null) {
304+
return null;
305+
}
306+
final String scheme = sslContext != null ? "https" : "http";
307+
final String authType = securityContextType != null ? securityContextType.name().toLowerCase() : "unknown";
308+
DatabaseClient client = null;
309+
try {
310+
client = supplier.get();
311+
return new TestResult(host, port, scheme, authType, username, client.checkConnection());
312+
} catch (Exception ex) {
313+
return new TestResult(host, port, scheme, authType, username, false, ex.getMessage());
314+
} finally {
315+
if (client != null) {
316+
client.release();
317+
}
318+
}
319+
}
320+
}

src/main/java/com/marklogic/mgmt/admin/AdminManager.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ public String getServerVersion() {
236236
return getServerConfig().getElementValue("/m:host/m:version");
237237
}
238238

239+
/**
240+
*
241+
* @return
242+
* @since 4.6.0
243+
*/
244+
public String getServerTimestamp() {
245+
return getServerConfig().getElementValue("/m:host/m:timestamp");
246+
}
247+
239248
public void setWaitForRestartCheckInterval(int waitForRestartCheckInterval) {
240249
this.waitForRestartCheckInterval = waitForRestartCheckInterval;
241250
}

src/main/java/com/marklogic/mgmt/cma/ConfigurationManager.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@
1515
*/
1616
package com.marklogic.mgmt.cma;
1717

18+
import com.fasterxml.jackson.databind.JsonNode;
1819
import com.marklogic.mgmt.AbstractManager;
1920
import com.marklogic.mgmt.ManageClient;
2021
import com.marklogic.mgmt.SaveReceipt;
2122
import com.marklogic.rest.util.MgmtResponseErrorHandler;
23+
import org.springframework.http.HttpMethod;
2224
import org.springframework.http.ResponseEntity;
2325
import org.springframework.web.client.HttpClientErrorException;
26+
import org.springframework.web.util.UriComponentsBuilder;
2427

2528
/**
2629
* This doesn't extend AbstractResourceManager because a configuration isn't really a resource, it's a collection of
2730
* resources.
28-
*
31+
* <p>
2932
* Currently only supports JSON and XML configuration payloads. Not clear yet from the docs on what the format of a
3033
* zip should be. The docs also mention a bunch of request parameters, but the examples don't show what the purpose
3134
* of those are, so those aren't supported yet either.
@@ -48,6 +51,7 @@ protected boolean useSecurityUser() {
4851
/**
4952
* Returns true if the CMA endpoint exists. This temporarily disables logging in MgmtResponseErrorHandler so that
5053
* a client doesn't see the 404 error being logged, which could be mistakenly perceived as a real error.
54+
*
5155
* @return
5256
*/
5357
public boolean endpointExists() {
@@ -96,4 +100,19 @@ public SaveReceipt submit(String payload) {
96100
return new SaveReceipt(null, payload, PATH, response);
97101
}
98102

103+
/**
104+
* @param resourceType
105+
* @return a JSON response containing details on each resource of the given type
106+
* @since 4.6.0
107+
*/
108+
public ResponseEntity<JsonNode> getResourcesAsJson(String resourceType) {
109+
String uri = UriComponentsBuilder
110+
.fromUri(manageClient.buildUri("/manage/v3"))
111+
.queryParam("format", "json")
112+
.queryParam("resource-type", resourceType)
113+
.encode()
114+
.toUriString();
115+
116+
return manageClient.getRestTemplate().exchange(uri, HttpMethod.GET, null, JsonNode.class);
117+
}
99118
}

0 commit comments

Comments
 (0)