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

Commit 7fac8db

Browse files
committed
#269 Can create forests via CMA
1 parent 417a08b commit 7fac8db

File tree

8 files changed

+148
-16
lines changed

8 files changed

+148
-16
lines changed

src/main/java/com/marklogic/appdeployer/AppConfig.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.marklogic.appdeployer.command.forests.ForestNamingStrategy;
44
import com.marklogic.client.DatabaseClient;
55
import com.marklogic.client.DatabaseClientFactory;
6-
import com.marklogic.client.DatabaseClientFactory.Authentication;
76
import com.marklogic.client.DatabaseClientFactory.SSLHostnameVerifier;
87
import com.marklogic.client.ext.ConfiguredDatabaseClientFactory;
98
import com.marklogic.client.ext.DatabaseClientConfig;
@@ -67,6 +66,11 @@ public class AppConfig {
6766
private boolean catchDeployExceptions = false;
6867
private boolean catchUndeployExceptions = false;
6968

69+
// Whether to optimize certain operations with the Configuration Management API (CMA) if it's available
70+
// For version 3.8.0, defaulting this to false as it's a new feature.
71+
// Will default it to true once we have more experience with CMA under our belts.
72+
private boolean optimizeWithCma = false;
73+
7074
// Used to construct DatabaseClient instances based on inputs defined in this class
7175
private ConfiguredDatabaseClientFactory configuredDatabaseClientFactory = new DefaultConfiguredDatabaseClientFactory();
7276

@@ -1204,4 +1208,12 @@ public Map<String, ForestNamingStrategy> getForestNamingStrategies() {
12041208
public void setForestNamingStrategies(Map<String, ForestNamingStrategy> forestNamingStrategies) {
12051209
this.forestNamingStrategies = forestNamingStrategies;
12061210
}
1211+
1212+
public boolean isOptimizeWithCma() {
1213+
return optimizeWithCma;
1214+
}
1215+
1216+
public void setOptimizeWithCma(boolean optimizeWithCma) {
1217+
this.optimizeWithCma = optimizeWithCma;
1218+
}
12071219
}

src/main/java/com/marklogic/appdeployer/DefaultAppConfigFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public void initialize() {
4040
config.setCatchUndeployExceptions(Boolean.parseBoolean(prop));
4141
});
4242

43+
propertyConsumerMap.put("mlOptimizeWithCma", (config, prop) -> {
44+
logger.info("Optimize with the Configuration Management API (CMA): " + prop);
45+
config.setOptimizeWithCma(Boolean.parseBoolean(prop));
46+
});
47+
4348
/**
4449
* The application name is used as a prefix for default names for a variety of resources, such as REST API servers
4550
* and databases.

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.marklogic.client.ext.helper.LoggingObject;
44
import com.marklogic.mgmt.PayloadParser;
55
import com.marklogic.mgmt.admin.AdminManager;
6+
import com.marklogic.mgmt.cma.ConfigurationManager;
67
import com.marklogic.mgmt.resource.ResourceManager;
78
import com.marklogic.mgmt.SaveReceipt;
89
import org.springframework.http.HttpHeaders;
@@ -274,6 +275,13 @@ protected void logResourceDirectoryNotFound(File dir) {
274275
}
275276
}
276277

278+
protected boolean shouldOptimizeWithCma(CommandContext context) {
279+
if (context.getAppConfig().isOptimizeWithCma()) {
280+
return new ConfigurationManager(context.getManageClient()).endpointExists();
281+
}
282+
return false;
283+
}
284+
277285
public void setPayloadTokenReplacer(PayloadTokenReplacer payloadTokenReplacer) {
278286
this.payloadTokenReplacer = payloadTokenReplacer;
279287
}

src/main/java/com/marklogic/appdeployer/command/forests/DeployForestsCommand.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import com.marklogic.appdeployer.command.CommandContext;
66
import com.marklogic.appdeployer.command.SortOrderConstants;
77
import com.marklogic.mgmt.api.forest.Forest;
8+
import com.marklogic.mgmt.cma.ConfigurationManager;
89
import com.marklogic.mgmt.resource.databases.DatabaseManager;
910
import com.marklogic.mgmt.resource.forests.ForestManager;
1011
import com.marklogic.mgmt.resource.hosts.DefaultHostNameProvider;
1112
import com.marklogic.mgmt.resource.hosts.HostManager;
13+
import org.springframework.http.ResponseEntity;
1214
import org.springframework.util.StringUtils;
1315

1416
import java.io.File;
@@ -51,6 +53,39 @@ public DeployForestsCommand(String databaseName) {
5153
public void execute(CommandContext context) {
5254
// Replicas are currently handled by ConfigureForestReplicasCommand
5355
List<Forest> forests = buildForests(context, false);
56+
57+
if (shouldOptimizeWithCma(context) && !forests.isEmpty()) {
58+
createForestsViaCma(context, forests);
59+
} else {
60+
if (logger.isInfoEnabled()) {
61+
logger.info("Configuration Management API is not available at " + ConfigurationManager.PATH + ", so forests will be created one at a time via the forests endpoint");
62+
}
63+
createForestsViaForestEndpoint(context, forests);
64+
}
65+
}
66+
67+
protected void createForestsViaCma(CommandContext context, List<Forest> forests) {
68+
StringBuilder sb = new StringBuilder("{\"config\":[{\"forest\":[");
69+
for (int i = 0; i < forests.size(); i++) {
70+
if (i > 0) {
71+
sb.append(",");
72+
}
73+
sb.append(forests.get(i).getJson());
74+
}
75+
sb.append("]}]}");
76+
77+
// For version 3.8.0, logging the configuration package at the info level for better visibility and
78+
// easier debugging of this new feature
79+
if (logger.isInfoEnabled()) {
80+
logger.info("Submitting configuration with forests: " + sb);
81+
}
82+
context.getManageClient().postJson("/manage/v3", sb.toString());
83+
if (logger.isInfoEnabled()) {
84+
logger.info("Successfully submitted configuration with forests");
85+
}
86+
}
87+
88+
protected void createForestsViaForestEndpoint(CommandContext context, List<Forest> forests) {
5489
ForestManager forestManager = new ForestManager(context.getManageClient());
5590
for (Forest f : forests) {
5691
forestManager.save(f.getJson());

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import com.marklogic.mgmt.AbstractManager;
44
import com.marklogic.mgmt.ManageClient;
55
import com.marklogic.mgmt.SaveReceipt;
6+
import com.marklogic.rest.util.MgmtResponseErrorHandler;
67
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.client.HttpClientErrorException;
79

810
/**
911
* This doesn't extend AbstractResourceManager because a configuration isn't really a resource, it's a collection of
@@ -15,12 +17,34 @@
1517
*/
1618
public class ConfigurationManager extends AbstractManager {
1719

20+
public final static String PATH = "/manage/v3";
21+
1822
private ManageClient manageClient;
1923

2024
public ConfigurationManager(ManageClient manageClient) {
2125
this.manageClient = manageClient;
2226
}
2327

28+
/**
29+
* Returns true if the CMA endpoint exists. This temporarily disables logging in MgmtResponseErrorHandler so that
30+
* a client doesn't see the 404 error being logged, which could be mistakenly perceived as a real error.
31+
* @return
32+
*/
33+
public boolean endpointExists() {
34+
try {
35+
MgmtResponseErrorHandler.errorLoggingEnabled = false;
36+
if (logger.isInfoEnabled()) {
37+
logger.info("Checking to see if Configuration Management API is available at: " + PATH);
38+
}
39+
manageClient.postJson(PATH, "{}");
40+
return true;
41+
} catch (HttpClientErrorException ex) {
42+
return false;
43+
} finally {
44+
MgmtResponseErrorHandler.errorLoggingEnabled = true;
45+
}
46+
}
47+
2448
public SaveReceipt save(String payload) {
2549
String configurationName = payloadParser.getPayloadFieldValue(payload, "name", false);
2650
if (configurationName == null) {
@@ -31,13 +55,13 @@ public SaveReceipt save(String payload) {
3155
logger.info("Applying configuration " + configurationName);
3256
}
3357

34-
final String path = "/manage/v3";
35-
ResponseEntity<String> response = postPayload(manageClient, path, payload);
58+
ResponseEntity<String> response = postPayload(manageClient, PATH, payload);
3659

3760
if (logger.isInfoEnabled()) {
3861
logger.info("Applied configuration " + configurationName);
3962
}
4063

41-
return new SaveReceipt(null, payload, path, response);
64+
return new SaveReceipt(null, payload, PATH, response);
4265
}
66+
4367
}

src/main/java/com/marklogic/rest/util/MgmtResponseErrorHandler.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,29 @@
1212

1313
public class MgmtResponseErrorHandler extends DefaultResponseErrorHandler {
1414

15-
protected final Logger logger = LoggerFactory.getLogger(getClass());
15+
protected final Logger logger = LoggerFactory.getLogger(getClass());
1616

17-
@Override
18-
public void handleError(ClientHttpResponse response) throws IOException {
19-
try {
20-
super.handleError(response);
21-
} catch (HttpClientErrorException | HttpServerErrorException ex) {
17+
/**
18+
* Allows clients to disable error logging. Typical use case is for when error logging isn't useful and may
19+
* confuse someone looking at the logs into thinking there's an actual problem.
20+
*/
21+
public static boolean errorLoggingEnabled = true;
22+
23+
@Override
24+
public void handleError(ClientHttpResponse response) throws IOException {
25+
try {
26+
super.handleError(response);
27+
} catch (HttpClientErrorException | HttpServerErrorException ex) {
2228
String message = "Logging HTTP response body to assist with debugging: " + ex.getResponseBodyAsString();
2329
if (HttpStatus.SERVICE_UNAVAILABLE.equals(ex.getStatusCode())) {
2430
if (logger.isDebugEnabled()) {
2531
logger.debug(message);
2632
}
33+
} else if (logger.isErrorEnabled() && errorLoggingEnabled) {
34+
logger.error(message);
2735
}
28-
else if (logger.isErrorEnabled()) {
29-
logger.error(message);
30-
}
31-
throw ex;
32-
}
33-
}
36+
throw ex;
37+
}
38+
}
3439

3540
}

src/test/java/com/marklogic/appdeployer/DefaultAppConfigFactoryTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public void allProperties() {
107107

108108
p.setProperty("mlCatchDeployExceptions", "true");
109109
p.setProperty("mlCatchUndeployExceptions", "true");
110+
p.setProperty("mlOptimizeWithCma", "true");
110111

111112
p.setProperty("mlHost", "prophost");
112113
p.setProperty("mlAppName", "propname");
@@ -201,6 +202,7 @@ public void allProperties() {
201202

202203
assertTrue(config.isCatchDeployExceptions());
203204
assertTrue(config.isCatchUndeployExceptions());
205+
assertTrue(config.isOptimizeWithCma());
204206

205207
assertEquals("prophost", config.getHost());
206208
assertEquals("propname", config.getName());
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.marklogic.appdeployer.command.forests;
2+
3+
import com.marklogic.appdeployer.AbstractAppDeployerTest;
4+
import com.marklogic.appdeployer.command.databases.DeployContentDatabasesCommand;
5+
import com.marklogic.mgmt.ManageClient;
6+
import com.marklogic.mgmt.cma.ConfigurationManager;
7+
import com.marklogic.mgmt.resource.clusters.ClusterManager;
8+
import com.marklogic.mgmt.resource.forests.ForestManager;
9+
import org.junit.Test;
10+
11+
import java.io.File;
12+
13+
public class DeployForestsViaCmaTest extends AbstractAppDeployerTest {
14+
15+
/**
16+
* We don't really know that the forests were created via CMA, as if the ML cluster isn't 9.0-5, the call to
17+
* /manage/v3 should fail silently and switch over to the forests endpoint for creating forests. The intent of this
18+
* test then is just to have a focused test on creating a handful of forests with inspection of logging done manually.
19+
*/
20+
@Test
21+
public void test() {
22+
//appConfig.setOptimizeWithCma(true);
23+
appConfig.getFirstConfigDir().setBaseDir(new File("src/test/resources/sample-app/db-only-config"));
24+
25+
initializeAppDeployer(new DeployContentDatabasesCommand(6));
26+
27+
ForestManager mgr = new ForestManager(manageClient);
28+
29+
try {
30+
deploySampleApp();
31+
for (int i = 1; i <= 6; i++) {
32+
assertTrue(mgr.exists(appConfig.getContentDatabaseName() + "-" + i));
33+
}
34+
} finally {
35+
undeploySampleApp();
36+
for (int i = 1; i <= 6; i++) {
37+
assertFalse(mgr.exists(appConfig.getContentDatabaseName() + "-" + i));
38+
}
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)