Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.
Merged
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 @@ -120,6 +120,11 @@ public Map<String, Object> getContextMap() {
return contextMap;
}

/**
* @param contextMap
* @deprecated since 4.6.0, will be removed in 5.0.0; the contextMap is not intended to be replaced.
*/
@Deprecated
public void setContextMap(Map<String, Object> contextMap) {
this.contextMap = contextMap;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
package com.marklogic.appdeployer.command;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.marklogic.appdeployer.AppConfig;
import com.marklogic.client.DatabaseClient;
import com.marklogic.client.ext.SecurityContextType;
import com.marklogic.mgmt.ManageClient;
import com.marklogic.mgmt.admin.AdminManager;
import com.marklogic.mgmt.cma.ConfigurationManager;
import com.marklogic.mgmt.resource.clusters.ClusterManager;
import com.marklogic.rest.util.RestConfig;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResponseErrorHandler;

import javax.net.ssl.SSLContext;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
* Command for testing each of the connections that can be made to MarkLogic based on the configuration in a
* {@code CommandContext}.
*
* @since 4.6.0
*/
public class TestConnectionsCommand extends AbstractCommand {

/**
* If run in a deployment process, this should run immediately so as to fail fast.
*/
public TestConnectionsCommand() {
setExecuteSortOrder(0);
}

/**
* Can be included in a deployment process so that the deployment fails if any of the connections fail.
*
* @param context
*/
@Override
public void execute(CommandContext context) {
TestResults results = testConnections(context);
if (results.anyTestFailed()) {
throw new RuntimeException(results.toString());
}
logger.info(results.toString());
}

/**
* Intended for execution outside a deployment process, where the client wants access to the test results and
* will choose how to present those to a user.
*
* @param context
* @return
*/
public TestResults testConnections(CommandContext context) {
try {
TestResult manageResult = testManageAppServer(context.getManageClient());
TestResult adminResult = testAdminAppServer(context.getAdminManager());

TestResult appServicesResult = null;
TestResult restResult = null;
TestResult testRestResult = null;
if (manageResult.isSucceeded()) {
List<Integer> serverPorts = getAppServerPorts(context.getManageClient());
appServicesResult = testAppServicesAppServer(context.getAppConfig(), serverPorts);
restResult = testRestAppServer(context.getAppConfig(), serverPorts);
testRestResult = testTestRestAppServer(context.getAppConfig(), serverPorts);
}

return new TestResults(manageResult, adminResult, appServicesResult, restResult, testRestResult);
} catch (Exception ex) {
// We don't expect any exceptions above, as each connection test has its own try/catch block.
// This is simply to pretty up the error a bit.
throw new RuntimeException("Unable to test connections; cause: " + ex.getMessage(), ex);
}
}

private List<Integer> getAppServerPorts(ManageClient manageClient) {
JsonNode json = new ConfigurationManager(manageClient).getResourcesAsJson("server").getBody();
ArrayNode servers = (ArrayNode) json.get("config").get(0).get("server");
List<Integer> ports = new ArrayList<>();
servers.forEach(server -> {
if (server.has("port")) {
ports.add(server.get("port").asInt());
}
});
return ports;
}

private TestResult testAppServicesAppServer(AppConfig appConfig, List<Integer> serverPorts) {
if (appConfig.getAppServicesPort() != null && serverPorts.contains(appConfig.getAppServicesPort())) {
return testWithDatabaseClient(appConfig.getHost(), appConfig.getAppServicesPort(),
appConfig.getAppServicesSslContext(), appConfig.getAppServicesSecurityContextType(),
appConfig.getAppServicesUsername(), () -> appConfig.newAppServicesDatabaseClient(null));
}
return null;
}

private TestResult testRestAppServer(AppConfig appConfig, List<Integer> serverPorts) {
if (appConfig.getRestPort() != null && serverPorts.contains(appConfig.getRestPort())) {
return testWithDatabaseClient(appConfig.getHost(), appConfig.getRestPort(),
appConfig.getRestSslContext(), appConfig.getRestSecurityContextType(),
appConfig.getRestAdminUsername(), appConfig::newDatabaseClient);
}
return null;
}

private TestResult testTestRestAppServer(AppConfig appConfig, List<Integer> serverPorts) {
if (appConfig.getTestRestPort() != null && serverPorts.contains(appConfig.getTestRestPort())) {
return testWithDatabaseClient(appConfig.getHost(), appConfig.getTestRestPort(),
appConfig.getRestSslContext(), appConfig.getRestSecurityContextType(),
appConfig.getRestAdminUsername(), appConfig::newTestDatabaseClient);
}
return null;
}

public static class TestResults {
private TestResult manageTestResult;
private TestResult adminTestResult;
private TestResult appServicesTestResult;
private TestResult restServerTestResult;
private TestResult testRestServerTestResult;

public TestResults(TestResult manageTestResult, TestResult adminTestResult,
TestResult appServicesTestResult, TestResult restServerTestResult, TestResult testRestServerTestResult) {
this.manageTestResult = manageTestResult;
this.adminTestResult = adminTestResult;
this.appServicesTestResult = appServicesTestResult;
this.restServerTestResult = restServerTestResult;
this.testRestServerTestResult = testRestServerTestResult;
}

public boolean anyTestFailed() {
return Stream.of(manageTestResult, adminTestResult, appServicesTestResult, restServerTestResult, testRestServerTestResult)
.anyMatch(test -> test != null && !test.isSucceeded());
}

public TestResult getManageTestResult() {
return manageTestResult;
}

public TestResult getAdminTestResult() {
return adminTestResult;
}

public TestResult getAppServicesTestResult() {
return appServicesTestResult;
}

public TestResult getRestServerTestResult() {
return restServerTestResult;
}

public TestResult getTestRestServerTestResult() {
return testRestServerTestResult;
}

/**
* @return a multi-line summary of all the non-null test results. This is intended to provide a simple
* rendering of the test result data, suitable for use in ml-gradle.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Manage App Server\n").append(getManageTestResult())
.append("\n\nAdmin App Server\n").append(getAdminTestResult());
if (getManageTestResult().isSucceeded()) {
if (getAppServicesTestResult() != null) {
sb.append("\n\nApp-Services App Server\n").append(getAppServicesTestResult());
} else {
sb.append("\n\nNo test run for the App-Services App Server as either a port is not configured for it or it has not been deployed yet");
}
if (getRestServerTestResult() != null) {
sb.append("\n\nREST API App Server\n").append(getRestServerTestResult());
} else {
sb.append("\n\nNo test run for a REST API App Server as either a port is not configured for it or it has not been deployed yet.");
}
if (getTestRestServerTestResult() != null) {
sb.append("\n\nTest REST API App Server\n").append(getTestRestServerTestResult());
} else {
sb.append("\n\nNo test run for a Test REST API App Server as either a port is not configured for it or it has not been deployed yet.");
}
} else {
sb.append("\n\nCould not test connections against the App-Services or REST API App Servers " +
"due to the Manage App Server connection failing.");
}
return sb.toString();
}
}

public static class TestResult {
private String host;
private int port;
private String scheme;
private String authType;
private String username;
private boolean succeeded;
private String message;

public TestResult(RestConfig restConfig, boolean succeeded, String message) {
this(restConfig.getHost(), restConfig.getPort(), restConfig.getScheme(), restConfig.getAuthType(),
restConfig.getUsername(), succeeded, message);
}

public TestResult(RestConfig restConfig, Exception ex) {
this(restConfig, false, ex.getMessage());
}

public TestResult(String host, int port, String scheme, String authType, String username, DatabaseClient.ConnectionResult result) {
this.host = host;
this.port = port;
this.scheme = scheme;
this.authType = authType;
this.username = username;
this.succeeded = result.isConnected();
if (!result.isConnected()) {
this.message = String.format("Received %d: %s", result.getStatusCode(), result.getErrorMessage());
}
}

public TestResult(String host, int port, String scheme, String authType, String username, boolean succeeded, String message) {
this.host = host;
this.port = port;
this.scheme = scheme;
this.authType = authType;
this.username = username;
this.succeeded = succeeded;
this.message = message;
}

public String getHost() {
return host;
}

public int getPort() {
return port;
}

public String getScheme() {
return scheme;
}

public boolean isSucceeded() {
return succeeded;
}

public String getMessage() {
return message;
}

public String getAuthType() {
return authType;
}

public String getUsername() {
return username;
}

/**
* @return a multi-line representation of the test result. This is intended to provide a simple
* rendering of the test result data, suitable for use in ml-gradle.
*/
@Override
public String toString() {
String result = String.format("Configured to connect to %s://%s:%d using '%s' authentication",
getScheme(), getHost(), getPort(), getAuthType());
if (getUsername() != null) {
result += String.format(" and username of '%s'", getUsername());
}
if (isSucceeded()) {
result += "\nConnected successfully";
return getMessage() != null ? result + "; " + getMessage() : result;
}
return result + "\nFAILED TO CONNECT; cause: " + message;
}
}

private TestResult testManageAppServer(ManageClient client) {
ResponseErrorHandler originalErrorHandler = client.getRestTemplate().getErrorHandler();
client.getRestTemplate().setErrorHandler(new DefaultResponseErrorHandler());
try {
String version = new ClusterManager(client).getVersion();
return new TestResult(client.getManageConfig(), true, "MarkLogic version: " + version);
} catch (Exception ex) {
if (ex instanceof HttpClientErrorException && ((HttpClientErrorException) ex).getRawStatusCode() == 404) {
return new TestResult(client.getManageConfig(), false,
"Unable to access /manage/v2; received 404; unexpected response: " + ex.getMessage());
} else {
return new TestResult(client.getManageConfig(), ex);
}
} finally {
client.getRestTemplate().setErrorHandler(originalErrorHandler);
}
}

private TestResult testAdminAppServer(AdminManager adminManager) {
try {
String timestamp = adminManager.getServerTimestamp();
return new TestResult(adminManager.getAdminConfig(), true, "MarkLogic server timestamp: " + timestamp);
} catch (Exception ex) {
return new TestResult(adminManager.getAdminConfig(), ex);
}
}

private TestResult testWithDatabaseClient(String host, Integer port, SSLContext sslContext,
SecurityContextType securityContextType, String username, Supplier<DatabaseClient> supplier) {
if (port == null) {
return null;
}
final String scheme = sslContext != null ? "https" : "http";
final String authType = securityContextType != null ? securityContextType.name().toLowerCase() : "unknown";
DatabaseClient client = null;
try {
client = supplier.get();
return new TestResult(host, port, scheme, authType, username, client.checkConnection());
} catch (Exception ex) {
return new TestResult(host, port, scheme, authType, username, false, ex.getMessage());
} finally {
if (client != null) {
client.release();
}
}
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/marklogic/mgmt/admin/AdminManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,15 @@ public String getServerVersion() {
return getServerConfig().getElementValue("/m:host/m:version");
}

/**
*
* @return
* @since 4.6.0
*/
public String getServerTimestamp() {
return getServerConfig().getElementValue("/m:host/m:timestamp");
}

public void setWaitForRestartCheckInterval(int waitForRestartCheckInterval) {
this.waitForRestartCheckInterval = waitForRestartCheckInterval;
}
Expand Down
Loading