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

Commit 810a17f

Browse files
committed
#431 Replicas now work with mlDatabasesWithForestsOnOneHost
HostCalculator now calculates two lists - one for primary forests, one for replica forests.
1 parent fef2c8f commit 810a17f

File tree

8 files changed

+131
-66
lines changed

8 files changed

+131
-66
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,13 @@ public boolean isDatabaseWithForestsOnOneHost(String databaseName) {
11511151
return databasesWithForestsOnOneHost.contains(databaseName);
11521152
}
11531153

1154+
public void addDatabaseWithForestsOnOneHost(String databaseName) {
1155+
if (databasesWithForestsOnOneHost == null) {
1156+
databasesWithForestsOnOneHost = new HashSet<>();
1157+
}
1158+
databasesWithForestsOnOneHost.add(databaseName);
1159+
}
1160+
11541161
public Set<String> getDatabasesWithForestsOnOneHost() {
11551162
return databasesWithForestsOnOneHost;
11561163
}

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

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

33
import com.marklogic.appdeployer.command.CommandContext;
44
import com.marklogic.client.ext.helper.LoggingObject;
5+
import com.marklogic.mgmt.api.forest.Forest;
56
import com.marklogic.mgmt.resource.hosts.HostNameProvider;
67

78
import java.util.ArrayList;
@@ -17,21 +18,44 @@ public DefaultHostCalculator(HostNameProvider hostNameProvider) {
1718
}
1819

1920
@Override
20-
public List<String> calculateHostNames(String databaseName, CommandContext context) {
21-
List<String> hostNamesFromDatabaseGroups = determineHostsNamesBasedOnDatabaseGroups(databaseName, context);
22-
if (hostNamesFromDatabaseGroups != null) {
23-
if (hostNamesFromDatabaseGroups.size() > 1 && context.getAppConfig().isDatabaseWithForestsOnOneHost(databaseName)) {
24-
return hostNamesFromDatabaseGroups.subList(0, 1);
21+
public ForestHostNames calculateHostNames(String databaseName, CommandContext context, List<Forest> existingPrimaryForests) {
22+
final List<String> candidateHostNames = getCandidateHostNames(databaseName, context);
23+
if (candidateHostNames.isEmpty()) {
24+
throw new RuntimeException("Unable to determine host names for forests for database: " + databaseName + "; please check the " +
25+
"properties you've set for creating forests for this database to ensure that forests can be created on at " +
26+
"least one host in your cluster");
27+
}
28+
29+
final List<String> primaryForestHostNames = new ArrayList<>();
30+
final List<String> replicaForestHostNames = new ArrayList<>();
31+
32+
if (context.getAppConfig().isDatabaseWithForestsOnOneHost(databaseName)) {
33+
if (existingPrimaryForests.size() > 0) {
34+
primaryForestHostNames.add(existingPrimaryForests.get(0).getHost());
35+
replicaForestHostNames.addAll(candidateHostNames);
36+
} else {
37+
primaryForestHostNames.add(candidateHostNames.get(0));
38+
replicaForestHostNames.addAll(candidateHostNames);
2539
}
26-
return hostNamesFromDatabaseGroups;
40+
} else {
41+
primaryForestHostNames.addAll(candidateHostNames);
42+
replicaForestHostNames.addAll(candidateHostNames);
2743
}
2844

45+
return new ForestHostNames(primaryForestHostNames, replicaForestHostNames);
46+
}
47+
48+
protected List<String> getCandidateHostNames(String databaseName, CommandContext context) {
2949
if (logger.isInfoEnabled()) {
3050
logger.info("Finding eligible hosts for forests for database: " + databaseName);
3151
}
3252

33-
List<String> hostNames = hostNameProvider.getHostNames();
53+
List<String> hostNamesFromDatabaseGroups = determineHostNamesBasedOnDatabaseGroups(databaseName, context);
54+
if (hostNamesFromDatabaseGroups != null) {
55+
return hostNamesFromDatabaseGroups;
56+
}
3457

58+
List<String> hostNames = hostNameProvider.getHostNames();
3559
List<String> hostNamesFromDatabaseHosts = determineHostNamesBasedOnDatabaseHosts(databaseName, context, hostNames);
3660
if (hostNamesFromDatabaseHosts != null) {
3761
return hostNamesFromDatabaseHosts;
@@ -44,7 +68,7 @@ public List<String> calculateHostNames(String databaseName, CommandContext conte
4468
* @param context
4569
* @return
4670
*/
47-
protected List<String> determineHostsNamesBasedOnDatabaseGroups(String databaseName, CommandContext context) {
71+
protected List<String> determineHostNamesBasedOnDatabaseGroups(String databaseName, CommandContext context) {
4872
Map<String, List<String>> databaseGroups = context.getAppConfig().getDatabaseGroups();
4973
if (databaseGroups != null) {
5074
List<String> selectedGroupNames = databaseGroups.get(databaseName);

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

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.ArrayList;
2020
import java.util.List;
2121
import java.util.Map;
22-
import java.util.Set;
2322

2423
/**
2524
* This command constructs forests based on properties found in the AppConfig object associated with the incoming
@@ -197,33 +196,17 @@ protected String buildForestTemplate(CommandContext context, ForestManager fores
197196
* configured to only have forests on one host, or when the deprecated setCreateForestsOnOneHost method is used.
198197
*/
199198
protected ForestHostNames determineHostNamesForForest(CommandContext context, List<Forest> existingPrimaryForests) {
200-
Set<String> databaseNames = context.getAppConfig().getDatabasesWithForestsOnOneHost();
201-
boolean onlyOnOneHost = databaseNames != null && databaseNames.contains(this.databaseName);
202-
203199
if (hostCalculator == null) {
204200
hostCalculator = new DefaultHostCalculator(new DefaultHostNameProvider(context.getManageClient()));
205201
}
206202

207-
// "candidate" = hosts that can be used for primary and replica forests
208-
List<String> candidateHostNames = hostCalculator.calculateHostNames(this.databaseName, context);
209-
List<String> primaryForestHostNames = new ArrayList<>();
210-
211-
if (!createForestsOnEachHost || onlyOnOneHost) {
212-
// If the database should only have forests on one host and such forests already exist, then use the host
213-
// that the forests exist on
214-
if (!existingPrimaryForests.isEmpty()) {
215-
primaryForestHostNames.add(existingPrimaryForests.get(0).getHost());
216-
}
217-
else {
218-
String first = candidateHostNames.get(0);
219-
logger.info(format("Only creating forests on the first host: " + first));
220-
primaryForestHostNames.add(first);
221-
}
222-
} else {
223-
primaryForestHostNames.addAll(candidateHostNames);
203+
// If this deprecated feature is used, then configure the AppConfig object so that the hostCalculator can be
204+
// aware of it.
205+
if (!createForestsOnEachHost) {
206+
context.getAppConfig().addDatabaseWithForestsOnOneHost(this.databaseName);
224207
}
225208

226-
return new ForestHostNames(primaryForestHostNames, candidateHostNames);
209+
return hostCalculator.calculateHostNames(this.databaseName, context, existingPrimaryForests);
227210
}
228211

229212
public int getForestsPerHost() {
@@ -285,21 +268,3 @@ public void setForestBuilder(ForestBuilder forestBuilder) {
285268
this.forestBuilder = forestBuilder;
286269
}
287270
}
288-
289-
class ForestHostNames {
290-
private List<String> primaryForestHostNames;
291-
private List<String> replicaForestHostNames;
292-
293-
public ForestHostNames(List<String> primaryForestHostNames, List<String> replicaForestHostNames) {
294-
this.primaryForestHostNames = primaryForestHostNames;
295-
this.replicaForestHostNames = replicaForestHostNames;
296-
}
297-
298-
public List<String> getPrimaryForestHostNames() {
299-
return primaryForestHostNames;
300-
}
301-
302-
public List<String> getReplicaForestHostNames() {
303-
return replicaForestHostNames;
304-
}
305-
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.marklogic.appdeployer.command.forests;
2+
3+
import java.util.List;
4+
5+
public class ForestHostNames {
6+
7+
private List<String> primaryForestHostNames;
8+
private List<String> replicaForestHostNames;
9+
10+
public ForestHostNames(List<String> primaryForestHostNames, List<String> replicaForestHostNames) {
11+
this.primaryForestHostNames = primaryForestHostNames;
12+
this.replicaForestHostNames = replicaForestHostNames;
13+
}
14+
15+
public List<String> getPrimaryForestHostNames() {
16+
return primaryForestHostNames;
17+
}
18+
19+
public List<String> getReplicaForestHostNames() {
20+
return replicaForestHostNames;
21+
}
22+
}
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
package com.marklogic.appdeployer.command.forests;
22

33
import com.marklogic.appdeployer.command.CommandContext;
4+
import com.marklogic.mgmt.api.forest.Forest;
45

56
import java.util.List;
67

78
public interface HostCalculator {
89

9-
List<String> calculateHostNames(String databaseName, CommandContext context);
10+
/**
11+
* Calculates which hosts can be used for primary and replica forests. The results are affected by the AppConfig
12+
* properties that configure which groups and hosts are allowed to have forests for a particular database.
13+
*
14+
* In addition, if the database is configured to only have forests on one host, then the list of primary forests
15+
* will have a single host. However, the list of replica forests will still have all candidate hosts so that
16+
* replicas can still be created.
17+
*
18+
* @param databaseName
19+
* @param context
20+
* @param existingPrimaryForests
21+
* @return
22+
*/
23+
ForestHostNames calculateHostNames(String databaseName, CommandContext context, List<Forest> existingPrimaryForests);
1024

1125
}

src/test/java/com/marklogic/appdeployer/command/forests/BuildForestReplicaTest.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import com.marklogic.mgmt.api.forest.Forest;
66
import com.marklogic.mgmt.api.forest.ForestReplica;
77
import com.marklogic.mgmt.util.SimplePropertySource;
8-
import static org.junit.jupiter.api.Assertions.*;
98
import org.junit.jupiter.api.Test;
109

1110
import java.util.Arrays;
@@ -15,7 +14,10 @@
1514
import java.util.concurrent.atomic.AtomicInteger;
1615
import java.util.stream.Stream;
1716

18-
public class BuildForestReplicaTest {
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
19+
20+
public class BuildForestReplicaTest {
1921

2022
private ForestBuilder builder = new ForestBuilder();
2123

@@ -120,6 +122,23 @@ public void primaryForestsOnOneHost() {
120122
});
121123
}
122124

125+
@Test
126+
void databaseHasPrimaryForestsOnOneHost() {
127+
AppConfig config = newAppConfig("mlDatabasesWithForestsOnOneHost", "my-database");
128+
129+
Forest forest = builder.buildForests(new ForestPlan("my-database", "host1", "host2", "host3").withReplicaCount(2), config).get(0);
130+
assertEquals("host1", forest.getHost(), "host1 should be selected since it's the first host");
131+
assertEquals(2, forest.getForestReplica().size(), "Expecting 2 replicas since replica count is 2");
132+
133+
ForestReplica replica = forest.getForestReplica().get(0);
134+
assertEquals("my-database-1-replica-1", replica.getReplicaName());
135+
assertEquals("host2", replica.getHost());
136+
137+
replica = forest.getForestReplica().get(1);
138+
assertEquals("my-database-1-replica-2", replica.getReplicaName());
139+
assertEquals("host3", replica.getHost());
140+
}
141+
123142
@Test
124143
public void customNamingStrategyWithDistributedStrategy() {
125144
AppConfig appConfig = newAppConfig("mlForestsPerHost", "my-database,2");

src/test/java/com/marklogic/appdeployer/command/forests/CalculateForestHostsTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static org.junit.jupiter.api.Assertions.*;
88
import org.junit.jupiter.api.Test;
99

10+
import java.util.ArrayList;
1011
import java.util.List;
1112
import java.util.Properties;
1213

@@ -25,7 +26,7 @@ public void test() {
2526
DefaultHostCalculator hostCalculator = new DefaultHostCalculator(hostNameProvider);
2627

2728
// Verify we get all 5 hosts back when nothing special is configured
28-
List<String> hostNames = hostCalculator.calculateHostNames("test-db", context);
29+
List<String> hostNames = hostCalculator.calculateHostNames("test-db", context, new ArrayList<>()).getPrimaryForestHostNames();
2930
assertEquals(5, hostNames.size());
3031

3132
// Select 2 of the 3 hosts for test-db
@@ -35,7 +36,7 @@ public void test() {
3536
appConfig = factory.newAppConfig();
3637
context = new CommandContext(appConfig, null, null);
3738

38-
hostNames = hostCalculator.calculateHostNames("test-db", context);
39+
hostNames = hostCalculator.calculateHostNames("test-db", context, new ArrayList<>()).getPrimaryForestHostNames();
3940
assertEquals(3, hostNames.size());
4041
assertTrue(hostNames.contains("name1"));
4142
assertTrue(hostNames.contains("name2"));
@@ -44,7 +45,7 @@ public void test() {
4445
props.setProperty("mlDatabaseGroups", "test-db,group3");
4546
appConfig = factory.newAppConfig();
4647
context = new CommandContext(appConfig, null, null);
47-
hostNames = hostCalculator.calculateHostNames("test-db", context);
48+
hostNames = hostCalculator.calculateHostNames("test-db", context, new ArrayList<>()).getPrimaryForestHostNames();
4849
assertEquals(2, hostNames.size());
4950
assertTrue(hostNames.contains("name4"));
5051
assertTrue(hostNames.contains("name5"));
@@ -63,7 +64,7 @@ public void databaseWithForestOnOneHost() {
6364
props.setProperty("mlDatabasesWithForestsOnOneHost", "test-db");
6465

6566
CommandContext context = new CommandContext(new DefaultAppConfigFactory(new SimplePropertySource(props)).newAppConfig(), null, null);
66-
List<String> hostNames = hostCalculator.calculateHostNames("test-db", context);
67+
List<String> hostNames = hostCalculator.calculateHostNames("test-db", context, new ArrayList<>()).getPrimaryForestHostNames();
6768
assertEquals(1, hostNames.size(), "The database should only have a forest on the first host");
6869
assertEquals("name2", hostNames.get(0));
6970
}

src/test/java/com/marklogic/appdeployer/command/forests/CreateForestsOnOneHostTest.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import com.marklogic.mgmt.util.SimplePropertySource;
88
import org.junit.jupiter.api.Test;
99

10-
import java.util.*;
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.List;
1113

1214
import static org.junit.jupiter.api.Assertions.assertEquals;
1315

@@ -25,25 +27,36 @@ public void test() {
2527

2628
ForestHostNames hostNames = command.determineHostNamesForForest(context, new ArrayList<>());
2729
assertEquals(3, hostNames.getPrimaryForestHostNames().size());
30+
assertEquals(3, hostNames.getReplicaForestHostNames().size());
2831

29-
command.setCreateForestsOnEachHost(false);
32+
appConfig.addDatabaseWithForestsOnOneHost("test-db");
3033
hostNames = command.determineHostNamesForForest(context, new ArrayList<>());
3134
assertEquals(1, hostNames.getPrimaryForestHostNames().size());
32-
assertEquals("host1", hostNames.getPrimaryForestHostNames().get(0));
33-
assertEquals(3, hostNames.getReplicaForestHostNames().size(),
34-
"When forests aren't created on each host, all hosts should still be available for replica forests, " +
35-
"with the expectation that the primary forest host will still not be used");
35+
assertEquals(3, hostNames.getReplicaForestHostNames().size());
36+
}
37+
38+
@Test
39+
void deprecatedWayOfCreatingForestsOnOneHost() {
40+
AppConfig appConfig = new AppConfig();
41+
CommandContext context = new CommandContext(appConfig, null, null);
42+
43+
DeployForestsCommand command = new DeployForestsCommand("test-db");
44+
45+
HostCalculator hostCalculator = new DefaultHostCalculator(new TestHostNameProvider("host1", "host2", "host3"));
46+
command.setHostCalculator(hostCalculator);
3647

3748
command.setCreateForestsOnEachHost(true);
38-
hostNames = command.determineHostNamesForForest(context, new ArrayList<>());
49+
ForestHostNames hostNames = command.determineHostNamesForForest(context, new ArrayList<>());
3950
assertEquals(3, hostNames.getPrimaryForestHostNames().size());
51+
assertEquals(3, hostNames.getReplicaForestHostNames().size());
4052

41-
Set<String> names = new HashSet<>();
42-
names.add("test-db");
43-
appConfig.setDatabasesWithForestsOnOneHost(names);
53+
command.setCreateForestsOnEachHost(false);
4454
hostNames = command.determineHostNamesForForest(context, new ArrayList<>());
4555
assertEquals(1, hostNames.getPrimaryForestHostNames().size());
46-
assertEquals(3, hostNames.getReplicaForestHostNames().size());
56+
assertEquals("host1", hostNames.getPrimaryForestHostNames().get(0));
57+
assertEquals(3, hostNames.getReplicaForestHostNames().size(),
58+
"When forests aren't created on each host, all hosts should still be available for replica forests, " +
59+
"with the expectation that the primary forest host will still not be used");
4760
}
4861

4962
/**

0 commit comments

Comments
 (0)