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

Commit 7a96ead

Browse files
committed
DEVEXP-565 Using map of forests to determining if forests have replicas
Refactored the call for getting a map of forests - moved it into CommandContext so it's easier to reuse by separate commands. Note that `DeployOtherDatabasesCommand` is being reverted back as no changes are needed there anymore.
1 parent 2390c7e commit 7a96ead

File tree

6 files changed

+89
-78
lines changed

6 files changed

+89
-78
lines changed

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@
2020
import com.marklogic.mgmt.admin.AdminManager;
2121
import com.marklogic.mgmt.api.configuration.Configuration;
2222
import com.marklogic.mgmt.api.configuration.Configurations;
23+
import com.marklogic.mgmt.api.forest.Forest;
24+
import com.marklogic.mgmt.resource.forests.ForestManager;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
2327

2428
import java.util.HashMap;
29+
import java.util.List;
2530
import java.util.Map;
2631

2732
/**
@@ -61,6 +66,36 @@ public void addCmaConfigurationToCombinedRequest(Configuration configuration) {
6166
}
6267
}
6368

69+
/**
70+
* Added to greatly speed up performance when getting details about all the existing primary forests for each
71+
* database in the cluster. In the event that anything fails while getting the forest details, the map won't be
72+
* added to the context and any code expecting to use the map will have to fall back to using /manage/v2.
73+
*
74+
* @since 4.5.3
75+
*/
76+
public Map<String, List<Forest>> getMapOfPrimaryForests() {
77+
if (!appConfig.getCmaConfig().isDeployForests()) {
78+
return null;
79+
}
80+
final String key = "ml-app-deployer-mapOfPrimaryForests";
81+
if (contextMap.containsKey(key)) {
82+
return (Map<String, List<Forest>>) contextMap.get(key);
83+
}
84+
Logger logger = LoggerFactory.getLogger(getClass());
85+
try {
86+
logger.info("Retrieving all forest details via CMA");
87+
long start = System.currentTimeMillis();
88+
Map<String, List<Forest>> mapOfPrimaryForests = new ForestManager(manageClient).getMapOfPrimaryForests();
89+
logger.info("Finished retrieving all forests details via CMA; duration: " + (System.currentTimeMillis() - start));
90+
contextMap.put(key, mapOfPrimaryForests);
91+
return mapOfPrimaryForests;
92+
} catch (Exception ex) {
93+
logger.warn("Unable to retrieve all forest details, cause: " + ex.getMessage() + "; will fall back to " +
94+
"using /manage/v2 when needed for getting details for a forest.");
95+
return null;
96+
}
97+
}
98+
6499
public Configurations getCombinedCmaRequest() {
65100
return (Configurations) contextMap.get(COMBINED_CMA_REQUEST_KEY);
66101
}

src/main/java/com/marklogic/appdeployer/command/databases/DeployOtherDatabasesCommand.java

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,13 @@
3434
import com.marklogic.mgmt.mapper.DefaultResourceMapper;
3535
import com.marklogic.mgmt.mapper.ResourceMapper;
3636
import com.marklogic.mgmt.resource.databases.DatabaseManager;
37-
import com.marklogic.mgmt.resource.forests.ForestManager;
3837
import com.marklogic.mgmt.util.ObjectMapperFactory;
3938
import com.marklogic.rest.util.JsonNodeUtil;
4039

4140
import java.io.File;
4241
import java.io.FilenameFilter;
4342
import java.io.IOException;
44-
import java.util.ArrayList;
45-
import java.util.Collections;
46-
import java.util.HashMap;
47-
import java.util.HashSet;
48-
import java.util.LinkedHashMap;
49-
import java.util.List;
50-
import java.util.Map;
51-
import java.util.Set;
52-
import java.util.stream.Collectors;
43+
import java.util.*;
5344

5445
/**
5546
* As of release 3.14.0, this now handles all databases, not just "databases other than the default content database".
@@ -170,7 +161,7 @@ public void undo(CommandContext context) {
170161
/**
171162
* If no database files are found, may still need to delete the content database in case no file exists for it.
172163
* That's because the command for creating a REST API server will not delete the content database by default.
173-
* <p>
164+
*
174165
* Per ticket #404, this will now do a check to see if the default content database filename is ignored. If so,
175166
* and there are no database files found, then the content database will not be deleted.
176167
*
@@ -401,8 +392,6 @@ protected void buildDeployDatabaseCommands(CommandContext context, List<Database
401392
* @param databasePlans
402393
*/
403394
protected void deployDatabasesAndForestsViaCma(CommandContext context, List<DatabasePlan> databasePlans) {
404-
addForestMapToCommandContext(context, databasePlans);
405-
406395
Configuration dbConfig = new Configuration();
407396
// Forests must be included in a separate configuration object
408397
Configuration forestConfig = new Configuration();
@@ -426,28 +415,6 @@ protected void deployDatabasesAndForestsViaCma(CommandContext context, List<Data
426415
});
427416
}
428417

429-
/**
430-
* Added to greatly speed up performance when getting details about all the existing primary forests for each
431-
* database referenced by a plan. In the event that anything fails, the map won't be added to the command context
432-
* and any code expecting to use the map will just have to fall back to use /manage/v2.
433-
*
434-
* @param context
435-
* @param databasePlans
436-
*/
437-
private void addForestMapToCommandContext(CommandContext context, List<DatabasePlan> databasePlans) {
438-
try {
439-
Set<String> dbNames = databasePlans.stream().map(plan -> plan.getDatabaseName()).collect(Collectors.toSet());
440-
logger.info("Retrieving all forest details via CMA");
441-
long start = System.currentTimeMillis();
442-
Map<String, List<Forest>> forestMap = new ForestManager(context.getManageClient()).getPrimaryForestsForDatabases(dbNames.toArray(new String[]{}));
443-
logger.info("Finished retrieving all forests details via CMA; duration: " + (System.currentTimeMillis() - start));
444-
context.getContextMap().put("ml-app-deployer-forestMap", forestMap);
445-
} catch (Exception ex) {
446-
logger.warn("Unable to retrieve all forest details, cause: " + ex.getMessage() + "; will fall back to " +
447-
"using /manage/v2 when needed for getting details for a forest.");
448-
}
449-
}
450-
451418
/**
452419
* Each DatabasePlan is expected to have constructed a DeployForestCommand, but not executed it. Each
453420
* DeployForestCommand can then be used to build a list of forests. All of those forests can be combined into a

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

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -185,22 +185,41 @@ protected List<Forest> determineForestsNeedingReplicas(String databaseName, Comm
185185
ResourceMapper resourceMapper = new DefaultResourceMapper(api);
186186

187187
List<Forest> forestsNeedingReplicas = new ArrayList<>();
188+
Map<String, List<Forest>> mapOfPrimaryForests = context.getMapOfPrimaryForests();
188189

189-
for (String forestName : dbMgr.getForestNames(databaseName)) {
190-
logger.info(format("Checking the status of forest %s to determine if it is a primary forest and whether or not it has replicas already.", forestName));
191-
ForestStatus status = forestManager.getForestStatus(forestName);
192-
if (!status.isPrimary()) {
193-
logger.info(format("Forest %s is not a primary forest, so not configuring replica forests", forestName));
194-
continue;
195-
}
196-
if (status.hasReplicas()) {
197-
logger.info(format("Forest %s already has replicas, so not configuring replica forests", forestName));
198-
continue;
199-
}
190+
/**
191+
* In both blocks below, a forest is not included if it already has replicas. This logic dates back to 2015,
192+
* and is likely due to uncertainty over the various scenarios that can occur if a forest does already have
193+
* replicas. At least as of August 2023, MarkLogic recommends a single replica per forest. Given that no users
194+
* have asked for this check to not be performed and based on MarkLogic's recommendation, it seems reasonable
195+
* to leave this check in for now. However, some ad hoc testing has indicated that this check is unnecessary
196+
* and that it appears to safe to vary the number of replicas per forest. So it likely would be beneficial to
197+
* remove this check at some point.
198+
*/
199+
if (mapOfPrimaryForests != null && mapOfPrimaryForests.containsKey(databaseName)) {
200+
mapOfPrimaryForests.get(databaseName).forEach(forest -> {
201+
boolean forestHasReplicasAlready = forest.getForestReplica() != null && !forest.getForestReplica().isEmpty();
202+
if (!forestHasReplicasAlready) {
203+
forestsNeedingReplicas.add(forest);
204+
}
205+
});
206+
} else {
207+
for (String forestName : dbMgr.getForestNames(databaseName)) {
208+
logger.info(format("Checking the status of forest %s to determine if it is a primary forest and whether or not it has replicas already.", forestName));
209+
ForestStatus status = forestManager.getForestStatus(forestName);
210+
if (!status.isPrimary()) {
211+
logger.info(format("Forest %s is not a primary forest, so not configuring replica forests", forestName));
212+
continue;
213+
}
214+
if (status.hasReplicas()) {
215+
logger.info(format("Forest %s already has replicas, so not configuring replica forests", forestName));
216+
continue;
217+
}
200218

201-
String forestJson = forestManager.getPropertiesAsJson(forestName);
202-
Forest forest = resourceMapper.readResource(forestJson, Forest.class);
203-
forestsNeedingReplicas.add(forest);
219+
String forestJson = forestManager.getPropertiesAsJson(forestName);
220+
Forest forest = resourceMapper.readResource(forestJson, Forest.class);
221+
forestsNeedingReplicas.add(forest);
222+
}
204223
}
205224

206225
return forestsNeedingReplicas;

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,9 @@ public List<Forest> buildForests(CommandContext context, boolean includeReplicas
132132

133133
// As of 4.5.3, if CMA is enabled, then the context should contain a map of all the forests for each database
134134
// being deployed. If it's not there, then /manage/v2 will be used instead.
135-
if (context.getAppConfig().getCmaConfig().isDeployForests()) {
136-
Map<String, List<Forest>> map = (Map<String, List<Forest>>) context.getContextMap().get("ml-app-deployer-forestMap");
137-
if (map != null && map.containsKey(this.databaseName)) {
138-
existingPrimaryForests = map.get(this.databaseName);
139-
}
135+
Map<String, List<Forest>> mapOfPrimaryForests = context.getMapOfPrimaryForests();
136+
if (mapOfPrimaryForests != null && mapOfPrimaryForests.containsKey(this.databaseName)) {
137+
existingPrimaryForests = mapOfPrimaryForests.get(this.databaseName);
140138
}
141139

142140
if (existingPrimaryForests == null) {

src/main/java/com/marklogic/mgmt/resource/forests/ForestManager.java

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.springframework.web.util.UriComponentsBuilder;
2929

3030
import java.util.ArrayList;
31-
import java.util.Arrays;
3231
import java.util.HashMap;
3332
import java.util.Iterator;
3433
import java.util.List;
@@ -61,40 +60,36 @@ public ForestManager(ManageClient client) {
6160
}
6261

6362
/**
64-
* Uses CMA to get back all forests in the cluster. Then returns a map where keys are the given database names,
63+
* Uses CMA to get back all forests in the cluster. Then returns a map where keys are the database names,
6564
* and each key has a list of primary forests for that database.
6665
*
67-
* @param databaseNames
6866
* @return
6967
* @since 4.5.3
7068
*/
71-
public Map<String, List<Forest>> getPrimaryForestsForDatabases(String... databaseNames) {
69+
public Map<String, List<Forest>> getMapOfPrimaryForests() {
7270
String uri = UriComponentsBuilder.fromUri(getManageClient().buildUri("/manage/v3"))
7371
.queryParam("format", "json").queryParam("resource-type", "forest")
7472
.encode().toUriString();
7573

7674
JsonNode json = getManageClient().getRestTemplate().exchange(uri, HttpMethod.GET, null, JsonNode.class).getBody();
77-
// Config is an array of objects, and it should have a single object based on our request.
75+
// Config is an array of objects, and it will have a single object based on our request.
7876
ArrayNode allPrimaryForests = (ArrayNode) json.get("config").get(0).get("forest");
7977
ResourceMapper mapper = new DefaultResourceMapper(new API(getManageClient()));
8078

81-
List<String> dbNames = Arrays.asList(databaseNames);
82-
Map<String, List<Forest>> forestMap = new HashMap<>();
79+
Map<String, List<Forest>> mapOfPrimaryForests = new HashMap<>();
8380
allPrimaryForests.iterator().forEachRemaining(forest -> {
8481
if (forest.has("database")) {
8582
String db = forest.get("database").asText();
86-
if (dbNames.contains(db)) {
87-
List<Forest> forests = forestMap.get(db);
88-
if (forests == null) {
89-
forests = new ArrayList<>();
90-
forestMap.put(db, forests);
91-
}
92-
forests.add(mapper.readResource(forest.toString(), Forest.class));
83+
List<Forest> databaseForests = mapOfPrimaryForests.get(db);
84+
if (databaseForests == null) {
85+
databaseForests = new ArrayList<>();
86+
mapOfPrimaryForests.put(db, databaseForests);
9387
}
88+
databaseForests.add(mapper.readResource(forest.toString(), Forest.class));
9489
}
9590
});
96-
97-
return forestMap;
91+
92+
return mapOfPrimaryForests;
9893
}
9994

10095
public void createJsonForestWithName(String name, String host) {

src/test/java/com/marklogic/mgmt/resource/forests/GetPrimaryForestsTest.java renamed to src/test/java/com/marklogic/mgmt/resource/forests/GetMapOfPrimaryForestsTest.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@
88
import java.util.Map;
99
import java.util.Set;
1010

11-
import static org.junit.jupiter.api.Assertions.assertEquals;
1211
import static org.junit.jupiter.api.Assertions.assertTrue;
1312

14-
public class GetPrimaryForestsTest extends AbstractMgmtTest {
13+
public class GetMapOfPrimaryForestsTest extends AbstractMgmtTest {
1514

1615
/**
1716
* Simple test for verifying this method works for a couple OOTB databases. It is expected that the app-deployer
@@ -20,14 +19,12 @@ public class GetPrimaryForestsTest extends AbstractMgmtTest {
2019
*/
2120
@Test
2221
void test() {
23-
Map<String, List<Forest>> forestMap = new ForestManager(manageClient)
24-
.getPrimaryForestsForDatabases("App-Services", "Documents");
22+
Map<String, List<Forest>> mapOfPrimaryForests = new ForestManager(manageClient).getMapOfPrimaryForests();
2523

26-
Set<String> dbNames = forestMap.keySet();
24+
Set<String> dbNames = mapOfPrimaryForests.keySet();
2725
assertTrue(dbNames.contains("App-Services"));
2826
assertTrue(dbNames.contains("Documents"));
29-
assertEquals(2, dbNames.size());
30-
assertTrue(forestMap.get("App-Services").size() > 0);
31-
assertTrue(forestMap.get("Documents").size() > 0);
27+
assertTrue(mapOfPrimaryForests.get("App-Services").size() > 0);
28+
assertTrue(mapOfPrimaryForests.get("Documents").size() > 0);
3229
}
3330
}

0 commit comments

Comments
 (0)