clazz, final InputStream io) {
26 | val representer = new Representer();
27 | representer.getPropertyUtils().setSkipMissingProperties(true);
28 | Yaml yaml = new Yaml(
29 | new Constructor(clazz),
30 | representer
31 | );
32 | return yaml.load(io);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/core/Generated.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.core;
2 |
3 |
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 | import java.lang.annotation.Target;
8 |
9 | /**
10 | * Excludes marked methods from code coverage.
11 | *
12 | * Should be used only for experimental code.
13 | */
14 | @Retention(RetentionPolicy.RUNTIME)
15 | @Target(ElementType.METHOD)
16 | public @interface Generated {
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/exception/IncorrectUserInputException.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.exception;
2 |
3 | /**
4 | * @author Konstantin Silaev
5 | */
6 | public class IncorrectUserInputException extends RuntimeException {
7 |
8 | public IncorrectUserInputException(String errorMessage) {
9 | super(errorMessage);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/exception/MongoNodeInitializationException.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.exception;
2 |
3 | /**
4 | * @author Konstantin Silaev
5 | */
6 | public class MongoNodeInitializationException extends RuntimeException {
7 |
8 | public MongoNodeInitializationException(String errorMessage) {
9 | super(errorMessage);
10 | }
11 |
12 | public MongoNodeInitializationException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/ApplicationProperties.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Builder;
4 | import lombok.Getter;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Immutable class property class to evaluate them from different sources.
10 | *
11 | * @author Konstantin Silaev
12 | */
13 | @Getter
14 | @Builder
15 | public class ApplicationProperties {
16 | private final int replicaSetNumber;
17 | private final int awaitNodeInitAttempts;
18 | private final String mongoDockerImageName;
19 | private final boolean isEnabled;
20 | private final boolean addArbiter;
21 | private final boolean addToxiproxy;
22 | private final int slaveDelayTimeout;
23 | private final int slaveDelayNumber;
24 | private final boolean useHostDockerInternal;
25 | private final List commandLineOptions;
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/MongoDbVersion.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Value;
4 |
5 | /**
6 | * Immutable class to keep a Mongo Db version.
7 | *
8 | * @author Konstantin Silaev
9 | */
10 | @Value(staticConstructor = "of")
11 | public class MongoDbVersion {
12 | int major;
13 | int minor;
14 | int patch;
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/MongoNode.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Value;
4 |
5 | /**
6 | * Immutable class to load data via SnakeYml.
7 | * Describes a mongo node to use in public API.
8 | *
9 | * @author Konstantin Silaev
10 | */
11 | @Value(staticConstructor = "of")
12 | public class MongoNode {
13 | String ip;
14 | Integer port;
15 | Double health;
16 | ReplicaSetMemberState state;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/MongoNodeMutable.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | /**
9 | * Mutable class to load data via an external library.
10 | * Describes a mongo node constructed by SnakeYml.
11 | *
12 | * @author Konstantin Silaev
13 | */
14 | @Data
15 | @NoArgsConstructor
16 | @AllArgsConstructor
17 | @Builder
18 | public class MongoNodeMutable {
19 | private String name;
20 | private Double health;
21 | private Integer state;
22 | private String stateStr;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/MongoReplicaSetProperties.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Data;
4 | import lombok.NoArgsConstructor;
5 |
6 | /**
7 | * Mutable class to load data via an external library.
8 | * Describes a user defined properties constructed by SnakeYml.
9 | *
10 | * @author Konstantin Silaev
11 | */
12 | @Data
13 | @NoArgsConstructor
14 | public class MongoReplicaSetProperties {
15 | private Boolean enabled;
16 | private String mongoDockerImageName;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/MongoRsStatus.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Value;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * Immutable class to load data via SnakeYml.
9 | * Describing a mongo cluster to use in public API.
10 | *
11 | * @author Konstantin Silaev
12 | */
13 | @Value(staticConstructor = "of")
14 | public class MongoRsStatus {
15 | Integer status;
16 | MongoDbVersion version;
17 | List members;
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/MongoRsStatusMutable.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Data;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * Mutable class to load data via an external library.
9 | * Describes a mongo cluster constructed by SnakeYml.
10 | *
11 | * @author Konstantin Silaev
12 | */
13 | @Data
14 | public class MongoRsStatusMutable {
15 | private Integer status;
16 | private String version;
17 | private List members;
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/MongoSocketAddress.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Builder;
4 | import lombok.EqualsAndHashCode;
5 | import lombok.Getter;
6 | import lombok.ToString;
7 |
8 | /**
9 | * Immutable class representing a socket address for a mongo node.
10 | *
11 | * @author Konstantin Silaev
12 | */
13 | @EqualsAndHashCode(of = {"ip", "mappedPort"})
14 | @Builder
15 | @Getter
16 | @ToString
17 | public final class MongoSocketAddress {
18 | private final String ip;
19 | private final Integer replSetPort;
20 | private final Integer mappedPort;
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/Pair.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Value;
4 |
5 | /**
6 | * An immutable class containing 2 values.
7 | * Used in order to avoid a verbose AbstractMap.SimpleImmutableEntry or 3rd party libraries.
8 | *
9 | * @author Konstantin Silaev on 1/29/2020
10 | */
11 | @Value(staticConstructor = "of")
12 | public class Pair {
13 | L left;
14 | R right;
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/PropertyContainer.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | /**
8 | * Represents a mutable container for properties coming from a yml file.
9 | *
10 | * @author Konstantin Silaev
11 | */
12 | @Data
13 | @NoArgsConstructor
14 | @AllArgsConstructor
15 | public class PropertyContainer {
16 | private MongoReplicaSetProperties mongoReplicaSetProperties;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/ReplicaSetMemberState.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Getter;
4 | import lombok.RequiredArgsConstructor;
5 |
6 | /**
7 | * Represents states that a MongoDb node has as per:
8 | * Replica Set Member States
9 | *
10 | * @author Konstantin Silaev on 10/3/2019
11 | */
12 | @RequiredArgsConstructor
13 | public enum ReplicaSetMemberState {
14 | STARTUP(0),
15 | PRIMARY(1),
16 | SECONDARY(2),
17 | RECOVERING(3),
18 | STARTUP2(5),
19 | UNKNOWN(6),
20 | ARBITER(7),
21 | DOWN(8),
22 | ROLLBACK(9),
23 | NOT_RECOGNIZED(Integer.MAX_VALUE);
24 |
25 | @Getter
26 | private final int value;
27 |
28 | public static ReplicaSetMemberState getByValue(final int value) {
29 | for (ReplicaSetMemberState state : values()) {
30 | if (state.getValue() == value) {
31 | return state;
32 | }
33 | }
34 | return NOT_RECOGNIZED;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/model/UserInputProperties.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | import lombok.Builder;
4 | import lombok.Getter;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Basic input properties coming from MongoReplicaSet's builder.
10 | *
11 | * @author Konstantin Silaev
12 | */
13 | @Builder
14 | @Getter
15 | public final class UserInputProperties {
16 | private final Integer replicaSetNumber;
17 | private final Integer awaitNodeInitAttempts;
18 | private final String mongoDockerImageName;
19 | private final Boolean addArbiter;
20 | private final Boolean addToxiproxy;
21 | private final Integer slaveDelayTimeout;
22 | private final String propertyFileName;
23 | private final Integer slaveDelayNumber;
24 | private final Boolean useHostDockerInternal;
25 | private final List commandLineOptions;
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/service/ResourceService.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.service;
2 |
3 | import java.io.InputStream;
4 |
5 | public interface ResourceService {
6 | InputStream getResourceIO(final String fileName);
7 |
8 | String getString(final InputStream io);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/service/impl/ResourceServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.service.impl;
2 |
3 | import com.github.silaev.mongodb.replicaset.service.ResourceService;
4 | import lombok.SneakyThrows;
5 |
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.InputStream;
8 | import java.nio.charset.StandardCharsets;
9 |
10 | /**
11 | * Gets resource as a stream making it possible to mock such a call.
12 | * Also has some helper methods for the same reason.
13 | *
14 | * @author Konstantin Silaev
15 | */
16 | public class ResourceServiceImpl implements ResourceService {
17 | public InputStream getResourceIO(final String fileName) {
18 | return Thread.currentThread()
19 | .getContextClassLoader()
20 | .getResourceAsStream(fileName);
21 | }
22 |
23 | @SneakyThrows
24 | public String getString(final InputStream io) {
25 | final ByteArrayOutputStream result = new ByteArrayOutputStream();
26 | final byte[] buffer = new byte[1024];
27 | int length;
28 | while ((length = io.read(buffer)) != -1) {
29 | result.write(buffer, 0, length);
30 | }
31 | return result.toString(StandardCharsets.UTF_8.name());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/github/silaev/mongodb/replicaset/util/StringUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.util;
2 |
3 | import java.util.Optional;
4 |
5 | public class StringUtils {
6 | private StringUtils() {
7 | }
8 |
9 | public static boolean isBlank(String str) {
10 | int strLen;
11 | if (str != null && (strLen = str.length()) != 0) {
12 | for (int i = 0; i < strLen; ++i) {
13 | if (!Character.isWhitespace(str.charAt(i))) {
14 | return false;
15 | }
16 | }
17 | return true;
18 | } else {
19 | return true;
20 | }
21 | }
22 |
23 | public static String[] getArrayByDelimiter(final String s) {
24 | return Optional.ofNullable(s)
25 | .map(n -> n.split(":"))
26 | .orElseThrow(
27 | () -> new IllegalArgumentException("Parameter should not be null")
28 | );
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/MongoDbReplicaSetBuilderTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset;
2 |
3 | import com.github.silaev.mongodb.replicaset.converter.impl.UserInputToApplicationPropertiesConverter;
4 | import com.github.silaev.mongodb.replicaset.exception.IncorrectUserInputException;
5 | import lombok.val;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.function.Executable;
8 | import org.junit.jupiter.params.ParameterizedTest;
9 | import org.junit.jupiter.params.provider.ValueSource;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 | import static org.junit.jupiter.api.Assertions.assertEquals;
13 | import static org.junit.jupiter.api.Assertions.assertThrows;
14 |
15 | /**
16 | * Constructs a MongoReplicaSet via a builder and verifies
17 | * the result coming from different sources
18 | * (a system property, a yml file, default value).
19 | */
20 | class MongoDbReplicaSetBuilderTest {
21 |
22 | private static final String PREFIX = "mongoReplicaSetProperties.";
23 | private static final String DOCKER_IMAGE_NAME_PROPERTIES = PREFIX + "mongoDockerImageName";
24 | private static final String ENABLED_PROPERTIES = PREFIX + "enabled";
25 | private static final String USE_HOST_DOCKER_INTERNAL = PREFIX + "useHostDockerInternal";
26 |
27 | @Test
28 | void shouldGetDefaultReplicaSetNumber() {
29 | //GIVEN
30 | val replicaNumberExpected =
31 | UserInputToApplicationPropertiesConverter.REPLICA_SET_NUMBER_DEFAULT;
32 |
33 | //WHEN
34 | val replicaSet = MongoDbReplicaSet.builder().build();
35 |
36 | //THEN
37 | assertEquals(
38 | replicaNumberExpected,
39 | replicaSet.getReplicaSetNumber()
40 | );
41 | }
42 |
43 | @ParameterizedTest(name = "{index}: replicaSetNumber: {0}")
44 | @ValueSource(ints = {0, MongoDbReplicaSet.MAX_VOTING_MEMBERS + 1})
45 | void shouldThrowExceptionBecauseOfIncorrectReplicaSetNumber(
46 | final int replicaSetNumber
47 | ) {
48 | //GIVEN
49 | //replicaSetNumber
50 |
51 | //WHEN
52 | Executable executable =
53 | () -> MongoDbReplicaSet.builder().replicaSetNumber(replicaSetNumber).build();
54 |
55 | //THEN
56 | assertThrows(IncorrectUserInputException.class, executable);
57 | }
58 |
59 | @Test
60 | void shouldGetDefaultAwaitNodeInitAttempts() {
61 | //GIVEN
62 | val awaitExpected =
63 | UserInputToApplicationPropertiesConverter.AWAIT_NODE_INIT_ATTEMPTS;
64 |
65 | //WHEN
66 | val replicaSet = MongoDbReplicaSet.builder().build();
67 |
68 | //THEN
69 | assertEquals(
70 | awaitExpected,
71 | replicaSet.getAwaitNodeInitAttempts()
72 | );
73 | }
74 |
75 | @Test
76 | void shouldGetDefaultMongoDockerImageName() {
77 | //GIVEN
78 | val mongoDockerImageExpected =
79 | UserInputToApplicationPropertiesConverter.MONGO_DOCKER_IMAGE_DEFAULT;
80 |
81 | //WHEN
82 | val replicaSet = MongoDbReplicaSet.builder().build();
83 |
84 | //THEN
85 | assertEquals(
86 | mongoDockerImageExpected,
87 | replicaSet.getMongoDockerImageName()
88 | );
89 | }
90 |
91 | @Test
92 | void shouldGetUseHostDockerInternalFromProperty() {
93 | //GIVEN
94 | try {
95 | System.setProperty(USE_HOST_DOCKER_INTERNAL, "true");
96 |
97 | //WHEN
98 | val replicaSet = MongoDbReplicaSet.builder().build();
99 |
100 | //THEN
101 | assertThat(replicaSet.getUseHostDockerInternal()).isTrue();
102 | } finally {
103 | System.clearProperty(USE_HOST_DOCKER_INTERNAL);
104 | }
105 | }
106 |
107 | @Test
108 | void shouldGetUseHostDockerInternalFromInput() {
109 | //GIVEN
110 | try {
111 | System.setProperty(USE_HOST_DOCKER_INTERNAL, "false");
112 |
113 | //WHEN
114 | final MongoDbReplicaSet replicaSet = MongoDbReplicaSet.builder().useHostDockerInternal(true).build();
115 |
116 | //THEN
117 | assertThat(replicaSet.getUseHostDockerInternal()).isTrue();
118 | } finally {
119 | System.clearProperty(USE_HOST_DOCKER_INTERNAL);
120 | }
121 | }
122 |
123 | @Test
124 | void shouldGetUseHostDockerInternalDefault() {
125 | //GIVEN
126 | try {
127 | System.clearProperty(USE_HOST_DOCKER_INTERNAL);
128 |
129 | //WHEN
130 | val replicaSet = MongoDbReplicaSet.builder().build();
131 |
132 | //THEN
133 | assertThat(replicaSet.getUseHostDockerInternal()).isEqualTo(
134 | UserInputToApplicationPropertiesConverter.USE_HOST_DOCKER_INTERNAL_DEFAULT
135 | );
136 | } finally {
137 | System.clearProperty(USE_HOST_DOCKER_INTERNAL);
138 | }
139 | }
140 |
141 | @Test
142 | void shouldGetMongoDockerImageNameFromSystemProperty() {
143 | //GIVEN
144 | try {
145 | val mongoDockerFile = "mongo:4.2.0";
146 | System.setProperty(DOCKER_IMAGE_NAME_PROPERTIES, mongoDockerFile);
147 |
148 | //WHEN
149 | val replicaSet = MongoDbReplicaSet.builder().build();
150 |
151 | //THEN
152 | assertEquals(
153 | mongoDockerFile,
154 | replicaSet.getMongoDockerImageName()
155 | );
156 | } finally {
157 | System.clearProperty(DOCKER_IMAGE_NAME_PROPERTIES);
158 | }
159 | }
160 |
161 | @Test
162 | void shouldGetMongoDockerImageNameFromPropertyFile() {
163 | //GIVEN
164 | val propertyFileName = "enabled-false.yml";
165 | val mongoDockerFile = "mongo:4.1.13";
166 |
167 | //WHEN
168 | val replicaSet = MongoDbReplicaSet.builder()
169 | .propertyFileName(propertyFileName)
170 | .build();
171 |
172 | //THEN
173 | assertEquals(
174 | mongoDockerFile,
175 | replicaSet.getMongoDockerImageName()
176 | );
177 | }
178 |
179 | @Test
180 | void shouldGetDefaultIsEnabled() {
181 | //GIVEN
182 | val propertyFileName = "enabled-false.yml";
183 |
184 | //WHEN
185 | val replicaSet = MongoDbReplicaSet.builder()
186 | .propertyFileName(propertyFileName)
187 | .build();
188 |
189 | //THEN
190 | assertThat(replicaSet.isEnabled()).isFalse();
191 | }
192 |
193 | @Test
194 | void shouldIsEnabledFromPropertyFile() {
195 | //GIVEN
196 |
197 | //WHEN
198 | val replicaSet = MongoDbReplicaSet.builder().build();
199 |
200 | //THEN
201 | assertThat(replicaSet.isEnabled()).isTrue();
202 | }
203 |
204 | @Test
205 | void shouldGetDefaultAddArbiter() {
206 | //GIVEN
207 |
208 | //WHEN
209 | val replicaSet = MongoDbReplicaSet.builder().build();
210 |
211 | //THEN
212 | assertThat(replicaSet.getAddArbiter()).isFalse();
213 | }
214 |
215 | @Test
216 | void shouldGetEnabledFromSystemProperty() {
217 | //GIVEN
218 | try {
219 | val enabled = Boolean.FALSE;
220 | System.setProperty(ENABLED_PROPERTIES, enabled.toString());
221 |
222 | //WHEN
223 | val replicaSet = MongoDbReplicaSet.builder().build();
224 |
225 | //THEN
226 | assertEquals(enabled, replicaSet.isEnabled());
227 |
228 | } finally {
229 | System.clearProperty(ENABLED_PROPERTIES);
230 | }
231 | }
232 |
233 | @Test
234 | void shouldGetEnabledFromPropertyFile() {
235 | //GIVEN
236 | val enabled = Boolean.TRUE;
237 | val propertyFileName = "enabled-true.yml";
238 |
239 | //WHEN
240 | val replicaSet = MongoDbReplicaSet.builder()
241 | .propertyFileName(propertyFileName)
242 | .build();
243 |
244 | //THEN
245 | assertEquals(enabled, replicaSet.isEnabled());
246 | }
247 |
248 | @Test
249 | void shouldGetDefaultEnabled() {
250 | //GIVEN
251 | val enabled = Boolean.TRUE;
252 |
253 | //WHEN
254 | val replicaSet = MongoDbReplicaSet.builder().build();
255 |
256 | //THEN
257 | assertEquals(enabled, replicaSet.isEnabled());
258 | }
259 |
260 | @Test
261 | void shouldThrowExceptionBecauseOfSlaveDelayOnSingleNode() {
262 | //GIVEN
263 | //replicaSetNumber
264 |
265 | //WHEN
266 | Executable executable =
267 | () -> MongoDbReplicaSet.builder().replicaSetNumber(1).slaveDelayTimeout(60).build();
268 |
269 | //THEN
270 | assertThrows(IncorrectUserInputException.class, executable);
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/MongoDbReplicaSetTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset;
2 |
3 | import com.github.silaev.mongodb.replicaset.converter.impl.StringToMongoRsStatusConverter;
4 | import com.github.silaev.mongodb.replicaset.converter.impl.VersionConverter;
5 | import com.github.silaev.mongodb.replicaset.exception.IncorrectUserInputException;
6 | import com.github.silaev.mongodb.replicaset.exception.MongoNodeInitializationException;
7 | import com.github.silaev.mongodb.replicaset.model.MongoNode;
8 | import com.github.silaev.mongodb.replicaset.model.MongoRsStatus;
9 | import com.github.silaev.mongodb.replicaset.service.ResourceService;
10 | import com.github.silaev.mongodb.replicaset.service.impl.ResourceServiceImpl;
11 | import lombok.val;
12 | import org.junit.jupiter.api.BeforeEach;
13 | import org.junit.jupiter.api.Test;
14 | import org.junit.jupiter.api.extension.ExtendWith;
15 | import org.junit.jupiter.api.function.Executable;
16 | import org.junit.jupiter.params.ParameterizedTest;
17 | import org.junit.jupiter.params.provider.ValueSource;
18 | import org.mockito.Mock;
19 | import org.mockito.junit.jupiter.MockitoExtension;
20 | import org.testcontainers.containers.Container;
21 | import org.testcontainers.containers.GenericContainer;
22 | import org.testcontainers.containers.Network;
23 |
24 | import java.util.HashMap;
25 | import java.util.TreeMap;
26 |
27 | import static com.github.silaev.mongodb.replicaset.MongoDbReplicaSet.COMPARATOR_MAPPED_PORT;
28 | import static org.junit.jupiter.api.Assertions.assertThrows;
29 | import static org.mockito.Mockito.doReturn;
30 | import static org.mockito.Mockito.mock;
31 | import static org.mockito.Mockito.spy;
32 | import static org.mockito.Mockito.when;
33 |
34 | /**
35 | * @author Konstantin Silaev on 10/4/2019
36 | */
37 | @ExtendWith(MockitoExtension.class)
38 | class MongoDbReplicaSetTest {
39 | private static final int CONTAINER_EXIT_CODE_ERROR = -1;
40 | private final ResourceService resourceService = new ResourceServiceImpl();
41 | @Mock
42 | StringToMongoRsStatusConverter converter;
43 | private MongoDbReplicaSet replicaSet;
44 |
45 | @BeforeEach
46 | void setUp() {
47 | replicaSet = spy(
48 | new MongoDbReplicaSet(
49 | converter,
50 | new TreeMap<>(COMPARATOR_MAPPED_PORT),
51 | new HashMap<>(),
52 | new HashMap<>(),
53 | new HashMap<>(),
54 | mock(Network.class)
55 | ));
56 | }
57 |
58 | @Test
59 | void shouldNotGetReplicaSetUrl() {
60 | //GIVEN
61 | //replicaSet
62 |
63 | //WHEN
64 | Executable executable = replicaSet::getReplicaSetUrl;
65 |
66 | //THEN
67 | assertThrows(IllegalStateException.class, executable);
68 | }
69 |
70 | @Test
71 | void shouldNotGetMongoRsStatus() {
72 | //GIVEN
73 | //replicaSet
74 |
75 | //WHEN
76 | Executable executable = replicaSet::getMongoRsStatus;
77 |
78 | //THEN
79 | assertThrows(IllegalStateException.class, executable);
80 | }
81 |
82 | @Test
83 | void shouldNotCheckMongoNodeExitCodeAfterWaiting() {
84 | //GIVEN
85 | //replicaSet
86 |
87 | val container = mock(GenericContainer.class);
88 | val execResult = mock(Container.ExecResult.class);
89 | val nodeName = "nodeName";
90 | val awaitNodeInitAttempts = 29;
91 | when(execResult.getExitCode())
92 | .thenReturn(CONTAINER_EXIT_CODE_ERROR);
93 | val execResultStatusCommand = mock(Container.ExecResult.class);
94 | doReturn(execResultStatusCommand)
95 | .when(replicaSet)
96 | .execMongoDbCommandInContainer(container, MongoDbReplicaSet.STATUS_COMMAND);
97 | when(execResultStatusCommand.getStdout()).thenReturn("stdout");
98 |
99 | //WHEN
100 | Executable executable = () -> replicaSet.checkMongoNodeExitCodeAfterWaiting(
101 | container,
102 | execResult,
103 | nodeName,
104 | awaitNodeInitAttempts
105 | );
106 |
107 | //THEN
108 | assertThrows(MongoNodeInitializationException.class, executable);
109 | }
110 |
111 | @Test
112 | void shouldNotCheckMongoNodeExitCode() {
113 | //GIVEN
114 | //replicaSet
115 |
116 | val command = "command";
117 | val execResult = mock(Container.ExecResult.class);
118 | when(execResult.getExitCode())
119 | .thenReturn(CONTAINER_EXIT_CODE_ERROR);
120 | when(execResult.getStdout()).thenReturn("stdout");
121 |
122 | //WHEN
123 | Executable executable =
124 | () -> replicaSet.checkMongoNodeExitCode(execResult, command);
125 |
126 | //THEN
127 | assertThrows(MongoNodeInitializationException.class, executable);
128 | }
129 |
130 | @ParameterizedTest(name = "{index}: version: {0}")
131 | @ValueSource(strings = {"3.6.13", "3.4.22", "2.6.22"})
132 | void shouldNotTestVersion(final String inputVersion) {
133 | //GIVEN
134 | //inputVersion
135 | MongoRsStatus status = mock(MongoRsStatus.class);
136 | when(converter.convert(inputVersion)).thenReturn(status);
137 | when(status.getVersion())
138 | .thenReturn(new VersionConverter().convert(inputVersion));
139 |
140 | //WHEN
141 | Executable executable = () -> replicaSet.verifyVersion(inputVersion);
142 |
143 | //THEN
144 | assertThrows(IncorrectUserInputException.class, executable);
145 | }
146 |
147 | @Test
148 | void shouldTestFaultToleranceTestSupportAvailability() {
149 | //GIVEN
150 | doReturn(1).when(replicaSet).getReplicaSetNumber();
151 | val mongoNode = mock(MongoNode.class);
152 |
153 | //WHEN
154 | Executable executableWaitForAllMongoNodesUp =
155 | () -> replicaSet.waitForAllMongoNodesUp();
156 | Executable executableWaitForMasterReelection =
157 | () -> replicaSet.waitForMasterReelection(mongoNode);
158 | Executable executableStopNode =
159 | () -> replicaSet.stopNode(mongoNode);
160 | Executable executableKillNode =
161 | () -> replicaSet.killNode(mongoNode);
162 | Executable executableDisconnectNodeFromNetwork =
163 | () -> replicaSet.disconnectNodeFromNetwork(mongoNode);
164 | Executable executableConnectNodeToNetworkWithReconfiguration =
165 | () -> replicaSet.connectNodeToNetworkWithReconfiguration(mongoNode);
166 | Executable executableConnectNodeToNetwork =
167 | () -> replicaSet.connectNodeToNetwork(mongoNode);
168 |
169 | //THEN
170 | assertThrows(IllegalStateException.class, executableWaitForAllMongoNodesUp);
171 | assertThrows(IllegalStateException.class, executableStopNode);
172 | assertThrows(IllegalStateException.class, executableKillNode);
173 | assertThrows(IllegalStateException.class, executableWaitForMasterReelection);
174 | assertThrows(IllegalStateException.class, executableConnectNodeToNetwork);
175 | assertThrows(IllegalStateException.class, executableDisconnectNodeFromNetwork);
176 | assertThrows(IllegalStateException.class, executableConnectNodeToNetworkWithReconfiguration);
177 | }
178 |
179 | @Test
180 | void shouldNotCheckMongoNodeExitCodeAndStatus() {
181 | //GIVEN
182 | //replicaSet
183 |
184 | val command = "command";
185 | val execResult = mock(Container.ExecResult.class);
186 | when(execResult.getExitCode())
187 | .thenReturn(MongoDbReplicaSet.CONTAINER_EXIT_CODE_OK);
188 | val stdout = resourceService.getString(
189 | resourceService.getResourceIO("shell-output/timeout-exceeds.txt")
190 | );
191 | when(execResult.getStdout()).thenReturn(stdout);
192 | when(converter.convert(stdout)).thenReturn(MongoRsStatus.of(0, null, null));
193 |
194 | //WHEN
195 | Executable executable =
196 | () -> replicaSet.checkMongoNodeExitCodeAndStatus(execResult, command);
197 |
198 | //THEN
199 | assertThrows(MongoNodeInitializationException.class, executable);
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/converter/impl/MongoNodeToMongoSocketAddressConverterTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.converter.impl;
2 |
3 | import com.github.silaev.mongodb.replicaset.model.MongoNode;
4 | import com.github.silaev.mongodb.replicaset.model.ReplicaSetMemberState;
5 | import lombok.val;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import static org.junit.jupiter.api.Assertions.assertEquals;
9 | import static org.junit.jupiter.api.Assertions.assertNotNull;
10 | import static org.junit.jupiter.api.Assertions.assertNull;
11 |
12 | /**
13 | * @author Konstantin Silaev on 2/25/2020
14 | */
15 | class MongoNodeToMongoSocketAddressConverterTest {
16 | private final MongoNodeToMongoSocketAddressConverter converter =
17 | new MongoNodeToMongoSocketAddressConverter();
18 |
19 | @Test
20 | void shouldConvert() {
21 | //GIVEN
22 | final String ip = "ip";
23 | final int port = 27017;
24 | val mongoNode = MongoNode.of(ip, port, 1d, ReplicaSetMemberState.PRIMARY);
25 |
26 | //WHEN
27 | val socketAddress = converter.convert(mongoNode);
28 |
29 | //THEN
30 | assertNotNull(socketAddress);
31 | assertEquals(ip, socketAddress.getIp());
32 | assertEquals(port, socketAddress.getMappedPort());
33 | assertNull(socketAddress.getReplSetPort());
34 | }
35 |
36 | @Test
37 | void shouldConvertNullToNull() {
38 | //GIVEN
39 | final MongoNode mongoNode = null;
40 |
41 | //WHEN
42 | val socketAddress = converter.convert(mongoNode);
43 |
44 | //THEN
45 | assertNull(socketAddress);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/converter/impl/StringToMongoRsStatusConverterTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.converter.impl;
2 |
3 | import com.github.silaev.mongodb.replicaset.service.ResourceService;
4 | import com.github.silaev.mongodb.replicaset.service.impl.ResourceServiceImpl;
5 | import lombok.val;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.CsvSource;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | class StringToMongoRsStatusConverterTest {
12 | private final StringToMongoRsStatusConverter converter = new StringToMongoRsStatusConverter(
13 | new YmlConverterImpl(),
14 | new VersionConverter()
15 | );
16 | private final ResourceService resourceService = new ResourceServiceImpl();
17 |
18 | @ParameterizedTest(name = "shouldConvert: {index}, fileName: {0}")
19 | @CsvSource(value = {
20 | "shell-output/rs-status.txt, 1, 5",
21 | "shell-output/rs-status-framed.txt, 1, 3",
22 | "shell-output/rs-status-plain.txt, 1, 0",
23 | "shell-output/timeout-exceeds.txt, 0, 0"
24 | })
25 | void shouldConvert(final String fileName, final int status, final int membersNumber) {
26 | // GIVEN
27 | val rsStatus = resourceService.getString(resourceService.getResourceIO(fileName));
28 |
29 | // THEN
30 | val mongoRsStatusActual = converter.convert(rsStatus);
31 |
32 | // WHEN
33 | assertThat(mongoRsStatusActual).isNotNull();
34 | assertThat(mongoRsStatusActual.getStatus()).isEqualTo(status);
35 | val members = mongoRsStatusActual.getMembers();
36 | assertThat(members.size()).isEqualTo(membersNumber);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/converter/impl/UserInputToApplicationPropertiesConverterTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.converter.impl;
2 |
3 | import com.github.silaev.mongodb.replicaset.converter.YmlConverter;
4 | import com.github.silaev.mongodb.replicaset.exception.IncorrectUserInputException;
5 | import com.github.silaev.mongodb.replicaset.model.MongoReplicaSetProperties;
6 | import com.github.silaev.mongodb.replicaset.model.PropertyContainer;
7 | import com.github.silaev.mongodb.replicaset.model.UserInputProperties;
8 | import com.github.silaev.mongodb.replicaset.service.ResourceService;
9 | import lombok.val;
10 | import org.junit.jupiter.api.Test;
11 | import org.junit.jupiter.api.extension.ExtendWith;
12 | import org.junit.jupiter.api.function.Executable;
13 | import org.junit.jupiter.params.ParameterizedTest;
14 | import org.junit.jupiter.params.provider.MethodSource;
15 | import org.mockito.InjectMocks;
16 | import org.mockito.Mock;
17 | import org.mockito.junit.jupiter.MockitoExtension;
18 |
19 | import java.io.InputStream;
20 | import java.util.stream.Stream;
21 |
22 | import static org.junit.jupiter.api.Assertions.assertFalse;
23 | import static org.junit.jupiter.api.Assertions.assertNotNull;
24 | import static org.junit.jupiter.api.Assertions.assertNull;
25 | import static org.junit.jupiter.api.Assertions.assertThrows;
26 | import static org.mockito.Mockito.mock;
27 | import static org.mockito.Mockito.when;
28 |
29 | /**
30 | * @author Konstantin Silaev
31 | */
32 | @ExtendWith(MockitoExtension.class)
33 | class UserInputToApplicationPropertiesConverterTest {
34 | @Mock
35 | private YmlConverter ymlConverter;
36 |
37 | @Mock
38 | private ResourceService resourceService;
39 |
40 | @InjectMocks
41 | private UserInputToApplicationPropertiesConverter converter;
42 |
43 | static Stream blankOrNullStrings() {
44 | return Stream.of("", " ", null);
45 | }
46 |
47 | @Test
48 | void shouldGetFileProperties() {
49 | //GIVEN
50 | val propertyFileName = "propertyFileName.yml";
51 | val io = mock(InputStream.class);
52 | when(resourceService.getResourceIO(propertyFileName))
53 | .thenReturn(io);
54 | val propertyContainer = mock(PropertyContainer.class);
55 | when(ymlConverter.unmarshal(PropertyContainer.class, io))
56 | .thenReturn(propertyContainer);
57 | val mongoReplicaFileProperties = mock(MongoReplicaSetProperties.class);
58 | when(propertyContainer.getMongoReplicaSetProperties())
59 | .thenReturn(mongoReplicaFileProperties);
60 | when(mongoReplicaFileProperties.getEnabled()).thenReturn(Boolean.FALSE);
61 |
62 | //WHEN
63 | val mongoReplicaFilePropertiesActual =
64 | converter.getFileProperties(propertyFileName);
65 |
66 | //THEN
67 | assertFalse(mongoReplicaFilePropertiesActual.getEnabled());
68 | }
69 |
70 | @ParameterizedTest(name = "{index}: filePath: {0}")
71 | @MethodSource("blankOrNullStrings")
72 | void shouldGetDefaultsBecauseOfNullOrEmptyFile(String propertyFileName) {
73 | //GIVEN
74 | //propertyFileName
75 |
76 | //WHEN
77 |
78 | val mongoReplicaFilePropertiesActual =
79 | converter.getFileProperties(propertyFileName);
80 |
81 | //THEN
82 | assertNotNull(mongoReplicaFilePropertiesActual);
83 | assertNull(mongoReplicaFilePropertiesActual.getEnabled());
84 | }
85 |
86 | @Test
87 | void shouldNotEvaluateEnabledBecauseOfFileFormat() {
88 | //GIVEN
89 | val propertyFileName = "propertyFileName";
90 |
91 | //WHEN
92 | Executable executable =
93 | () -> converter.getFileProperties(propertyFileName);
94 |
95 | //THEN
96 | assertThrows(IllegalArgumentException.class, executable);
97 | }
98 |
99 | @Test
100 | void shouldNotConvertBecauseOfArbiterAndSingleNode() {
101 | //GIVEN
102 | val inputProperties = UserInputProperties.builder()
103 | .addArbiter(true)
104 | .replicaSetNumber(1)
105 | .build();
106 |
107 | //WHEN
108 | Executable executable = () -> converter.convert(inputProperties);
109 |
110 | //THEN
111 | assertThrows(IncorrectUserInputException.class, executable);
112 | }
113 |
114 | @Test
115 | void shouldNotConvertBecauseSlaveDelayNumberIsMoreThanReplicaSetNumber() {
116 | //GIVEN
117 | val inputProperties = UserInputProperties.builder()
118 | .slaveDelayNumber(6)
119 | .replicaSetNumber(4)
120 | .slaveDelayTimeout(5000)
121 | .build();
122 |
123 | //WHEN
124 | Executable executable = () -> converter.convert(inputProperties);
125 |
126 | //THEN
127 | assertThrows(IncorrectUserInputException.class, executable);
128 | }
129 |
130 | @Test
131 | void shouldNotConvertBecauseSlaveDelayTimeoutIsNotSet() {
132 | //GIVEN
133 | val inputProperties = UserInputProperties.builder()
134 | .slaveDelayNumber(6)
135 | .replicaSetNumber(4)
136 | .build();
137 |
138 | //WHEN
139 | Executable executable = () -> converter.convert(inputProperties);
140 |
141 | //THEN
142 | assertThrows(IncorrectUserInputException.class, executable);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/converter/impl/VersionConverterTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.converter.impl;
2 |
3 |
4 | import lombok.val;
5 | import org.junit.jupiter.api.function.Executable;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.MethodSource;
8 | import org.junit.jupiter.params.provider.ValueSource;
9 |
10 | import java.util.stream.Stream;
11 |
12 | import static org.junit.Assert.assertEquals;
13 | import static org.junit.jupiter.api.Assertions.assertThrows;
14 |
15 | /**
16 | * @author Konstantin Silaev
17 | */
18 | class VersionConverterTest {
19 | private final VersionConverter versionConverter = new VersionConverter();
20 |
21 | private static Stream incorrectVersions() {
22 | return Stream.of("3", "", null);
23 | }
24 |
25 | @ParameterizedTest(name = "{index}: version: {0}")
26 | @ValueSource(strings = {"3.6.14", "3.6"})
27 | void shouldConvert(String version) {
28 | //GIVEN
29 | String[] strings = version.split("\\.");
30 | val major = Integer.parseInt(strings[0]);
31 | val minor = Integer.parseInt(strings[1]);
32 | val patch = strings.length == 3 ? Integer.parseInt(strings[2]) : 0;
33 |
34 | //WHEN
35 | val mongoDbVersion = versionConverter.convert(version);
36 |
37 | //THEN
38 | assertEquals(major, mongoDbVersion.getMajor());
39 | assertEquals(minor, mongoDbVersion.getMinor());
40 | assertEquals(patch, mongoDbVersion.getPatch());
41 | }
42 |
43 | @ParameterizedTest(name = "{index}: version: {0}")
44 | @MethodSource("incorrectVersions")
45 | void shouldNotConvert(String version) {
46 | //GIVEN
47 | //version
48 |
49 | //WHEN
50 | Executable executable = () -> versionConverter.convert(version);
51 |
52 | //THEN
53 | assertThrows(IllegalArgumentException.class, executable);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/core/EnabledIfSystemPropertyEnabledByDefault.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.core;
2 |
3 | import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.ElementType;
8 | import java.lang.annotation.Retention;
9 | import java.lang.annotation.RetentionPolicy;
10 | import java.lang.annotation.Target;
11 |
12 |
13 | /**
14 | * Taken and slightly modified from {@link EnabledIfSystemProperty}
15 | *
16 | * As opposed to {@code @EnabledIfSystemProperty} if the specified system property is undefined,
17 | * the annotated class or method will be enabled.
18 | * Similar might be achieved by DisableIfSystemProperty with negation of a regexp.
19 | */
20 | @Target({ElementType.TYPE, ElementType.METHOD})
21 | @Retention(RetentionPolicy.RUNTIME)
22 | @Documented
23 | @ExtendWith(EnabledIfSystemPropertyEnabledByDefaultCondition.class)
24 | public @interface EnabledIfSystemPropertyEnabledByDefault {
25 |
26 | /**
27 | * The name of the JVM system property to retrieve.
28 | *
29 | * @return the system property name; never blank
30 | * @see System#getProperty(String)
31 | */
32 | String named();
33 |
34 | /**
35 | * A regular expression that will be used to match against the retrieved
36 | * value of the {@link #named} JVM system property.
37 | *
38 | * @return the regular expression; never blank
39 | * @see String#matches(String)
40 | * @see java.util.regex.Pattern
41 | */
42 | String matches();
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/core/EnabledIfSystemPropertyEnabledByDefaultCondition.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.core;
2 |
3 | import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
4 | import org.junit.jupiter.api.extension.ConditionEvaluationResult;
5 | import org.junit.jupiter.api.extension.ExecutionCondition;
6 | import org.junit.jupiter.api.extension.ExtensionContext;
7 | import org.junit.platform.commons.util.Preconditions;
8 |
9 | import java.util.Objects;
10 | import java.util.Optional;
11 |
12 | import static java.lang.String.format;
13 | import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled;
14 | import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled;
15 | import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;
16 |
17 | /**
18 | * Taken and slightly modified from {@link org.junit.jupiter.api.condition.EnabledIfSystemProperty}
19 | *
20 | * {@link ExecutionCondition} for {@link EnabledIfSystemPropertyExistsAndMatches @EnabledIfSystemPropertyExistsAndMatches}.
21 | *
22 | * @see EnabledIfSystemProperty
23 | */
24 | class EnabledIfSystemPropertyEnabledByDefaultCondition implements ExecutionCondition {
25 |
26 | private static final ConditionEvaluationResult ENABLED_BY_DEFAULT = enabled(
27 | "@EnabledIfSystemProperty is not present");
28 |
29 | @Override
30 | public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
31 | Optional optional = findAnnotation(context.getElement(),
32 | EnabledIfSystemPropertyEnabledByDefault.class);
33 |
34 | if (!optional.isPresent()) {
35 | return ENABLED_BY_DEFAULT;
36 | }
37 |
38 | EnabledIfSystemPropertyEnabledByDefault annotation = optional.get();
39 | String name = annotation.named().trim();
40 | String regex = annotation.matches();
41 | Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation);
42 | Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation);
43 | String actual = System.getProperty(name);
44 |
45 | // Nothing to match against?
46 | if (Objects.isNull(actual)) {
47 | return enabled(format("System property [%s] is enabled by default", name));
48 | }
49 | if (actual.matches(regex)) {
50 | return enabled(
51 | format("System property [%s] with value [%s] matches regular expression [%s]", name, actual, regex));
52 | }
53 | return disabled(
54 | format("System property [%s] with value [%s] does not match regular expression [%s]", name, actual, regex));
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/core/IntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.core;
2 |
3 | import org.junit.jupiter.api.Tag;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Target(ElementType.TYPE)
12 | @Tag("integration-test")
13 | public @interface IntegrationTest {
14 | }
15 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/PropertyEvaluationITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import static org.junit.jupiter.api.Assertions.assertFalse;
8 |
9 | @IntegrationTest
10 | class PropertyEvaluationITTest {
11 | private static final MongoDbReplicaSet MONGO_REPLICA_SET = MongoDbReplicaSet.builder()
12 | .replicaSetNumber(1)
13 | .awaitNodeInitAttempts(30)
14 | .propertyFileName("enabled-false.yml")
15 | .build();
16 |
17 | @Test
18 | void shouldGetProperties() {
19 | // GIVEN
20 | //MONGO_REPLICA_SET
21 |
22 | // WHEN
23 | // MONGO_REPLICA_SET is initialized
24 |
25 | // THEN
26 | assertFalse(MONGO_REPLICA_SET.isEnabled());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/VersionSupportTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
5 | import com.github.silaev.mongodb.replicaset.exception.IncorrectUserInputException;
6 | import lombok.val;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.function.Executable;
9 |
10 | import static org.junit.jupiter.api.Assertions.assertThrows;
11 |
12 | /**
13 | * @author Konstantin Silaev on 10/4/2019
14 | */
15 | @IntegrationTest
16 | class VersionSupportTest {
17 | @Test
18 | void shouldNotValidateVersion() {
19 | //GIVEN
20 | val replicaSet = MongoDbReplicaSet.builder()
21 | .mongoDockerImageName("mongo:3.4.22")
22 | .build();
23 |
24 | //WHEN
25 | Executable executable = replicaSet::start;
26 |
27 | //THEN
28 | assertThrows(IncorrectUserInputException.class, executable);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/api/BaseMongoDbReplicaSetApiITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.api;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.model.MongoNode;
5 | import com.github.silaev.mongodb.replicaset.model.ReplicaSetMemberState;
6 | import com.github.silaev.mongodb.replicaset.util.CollectionUtils;
7 | import com.github.silaev.mongodb.replicaset.util.ConnectionUtils;
8 | import com.github.silaev.mongodb.replicaset.util.StringUtils;
9 | import com.github.silaev.mongodb.replicaset.util.SubscriberHelperUtils;
10 | import com.mongodb.reactivestreams.client.MongoClients;
11 | import lombok.SneakyThrows;
12 | import lombok.extern.slf4j.Slf4j;
13 | import lombok.val;
14 | import org.bson.Document;
15 | import org.jetbrains.annotations.NotNull;
16 |
17 | import java.util.List;
18 | import java.util.concurrent.TimeUnit;
19 | import java.util.stream.Collectors;
20 |
21 | import static org.junit.jupiter.api.Assertions.assertEquals;
22 | import static org.junit.jupiter.api.Assertions.assertNotNull;
23 | import static org.junit.jupiter.api.Assertions.assertTrue;
24 |
25 | @Slf4j
26 | abstract class BaseMongoDbReplicaSetApiITTest {
27 | @SneakyThrows
28 | void shouldTestRsStatus(
29 | final MongoDbReplicaSet replicaSet,
30 | final int replicaSetNumber
31 | ) {
32 | // GIVEN
33 | //replicaSet
34 | val mongoRsUrl = replicaSet.getReplicaSetUrl();
35 | val mongoRsStatus = replicaSet.getMongoRsStatus();
36 | assertNotNull(mongoRsUrl);
37 | assertNotNull(mongoRsStatus);
38 |
39 | try (
40 | val mongoReactiveClient = MongoClients.create(
41 | ConnectionUtils.getMongoClientSettingsWithTimeout(mongoRsUrl)
42 | )
43 | ) {
44 | val db = mongoReactiveClient.getDatabase("admin");
45 |
46 | // WHEN + THEN
47 | val subscriber = SubscriberHelperUtils.getSubscriber(
48 | db.runCommand(new Document("replSetGetStatus", 1))
49 | );
50 | val document = getDocument(subscriber.get(5, TimeUnit.SECONDS));
51 | assertEquals(Double.valueOf("1"), document.get("ok", Double.class));
52 | val mongoNodesActual = extractMongoNodes(document.getList("members", Document.class));
53 |
54 | assertTrue(
55 | CollectionUtils.isEqualCollection(
56 | mongoRsStatus.getMembers(),
57 | mongoNodesActual
58 | )
59 | );
60 | assertEquals(
61 | replicaSetNumber + (replicaSet.getAddArbiter() ? 1 : 0),
62 | mongoNodesActual.size());
63 | }
64 | }
65 |
66 | private List extractMongoNodes(final List members) {
67 | return members.stream()
68 | .map(this::getMongoNode)
69 | .collect(Collectors.toList());
70 | }
71 |
72 | private MongoNode getMongoNode(final Document document) {
73 | val addresses =
74 | StringUtils.getArrayByDelimiter(document.getString("name"));
75 | return MongoNode.of(
76 | addresses[0],
77 | Integer.parseInt(addresses[1]),
78 | document.getDouble("health"),
79 | ReplicaSetMemberState.getByValue(document.getInteger("state"))
80 | );
81 | }
82 |
83 | @NotNull
84 | private Document getDocument(final List documents) {
85 | return documents.get(0);
86 | }
87 |
88 | void shouldTestEnabled(final MongoDbReplicaSet replicaSet) {
89 | // GIVEN
90 | val mongoRsUrl = replicaSet.getReplicaSetUrl();
91 | val mongoRsStatus = replicaSet.getMongoRsStatus();
92 |
93 | // WHEN
94 | // replicaSet is initialized
95 |
96 | // THEN
97 | assertNotNull(mongoRsUrl);
98 | assertNotNull(mongoRsStatus);
99 | assertTrue(replicaSet.isEnabled());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/api/MongoDbReplicaSetMultiNodeApiITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.api;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.junit.jupiter.api.AfterAll;
7 | import org.junit.jupiter.api.BeforeAll;
8 | import org.junit.jupiter.api.Test;
9 |
10 | @IntegrationTest
11 | @Slf4j
12 | class MongoDbReplicaSetMultiNodeApiITTest extends
13 | BaseMongoDbReplicaSetApiITTest {
14 | private static final int REPLICA_SET_NUMBER = 2;
15 |
16 | private static final MongoDbReplicaSet MONGO_REPLICA_SET = MongoDbReplicaSet.builder()
17 | .replicaSetNumber(REPLICA_SET_NUMBER)
18 | .addArbiter(true)
19 | .build();
20 |
21 | @BeforeAll
22 | static void setUpAll() {
23 | MONGO_REPLICA_SET.start();
24 | }
25 |
26 | @AfterAll
27 | static void tearDownAll() {
28 | MONGO_REPLICA_SET.stop();
29 | }
30 |
31 | @Test
32 | void shouldTestRsStatus() {
33 | super.shouldTestRsStatus(MONGO_REPLICA_SET, REPLICA_SET_NUMBER);
34 | }
35 |
36 | @Test
37 | void shouldTestEnabled() {
38 | super.shouldTestEnabled(MONGO_REPLICA_SET);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/api/MongoDbReplicaSetSingleNodeApiITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.api;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.junit.jupiter.api.AfterAll;
7 | import org.junit.jupiter.api.BeforeAll;
8 | import org.junit.jupiter.api.Test;
9 |
10 | @IntegrationTest
11 | @Slf4j
12 | class MongoDbReplicaSetSingleNodeApiITTest extends
13 | BaseMongoDbReplicaSetApiITTest {
14 | private static final int REPLICA_SET_NUMBER = 1;
15 |
16 | private static final MongoDbReplicaSet MONGO_REPLICA_SET = MongoDbReplicaSet.builder()
17 | .replicaSetNumber(REPLICA_SET_NUMBER)
18 | .build();
19 |
20 | @BeforeAll
21 | static void setUpAll() {
22 | MONGO_REPLICA_SET.start();
23 | }
24 |
25 | @AfterAll
26 | static void tearDownAll() {
27 | MONGO_REPLICA_SET.stop();
28 | }
29 |
30 | @Test
31 | void shouldTestRsStatus() {
32 | super.shouldTestRsStatus(MONGO_REPLICA_SET, REPLICA_SET_NUMBER);
33 | }
34 |
35 | @Test
36 | void shouldTestEnabled() {
37 | super.shouldTestEnabled(MONGO_REPLICA_SET);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/api/faulttolerance/MongoDbDelayedMembersITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.api.faulttolerance;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.EnabledIfSystemPropertyEnabledByDefault;
5 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
6 | import com.github.silaev.mongodb.replicaset.model.ReplicaSetMemberState;
7 | import lombok.extern.slf4j.Slf4j;
8 | import lombok.val;
9 | import org.junit.jupiter.api.Disabled;
10 | import org.junit.jupiter.api.Test;
11 |
12 | import java.util.Arrays;
13 |
14 | import static org.hamcrest.CoreMatchers.hasItems;
15 | import static org.hamcrest.MatcherAssert.assertThat;
16 | import static org.junit.jupiter.api.Assertions.assertNotNull;
17 |
18 | /**
19 | * Simulates a situation when a replica set with a delayed member
20 | * is reconfigured to a non-delayed one with or without losing primary first.
21 | *
22 | * @author Konstantin Silaev on 5/8/2020
23 | */
24 | @IntegrationTest
25 | @Slf4j
26 | @EnabledIfSystemPropertyEnabledByDefault(
27 | named = "mongoReplicaSetProperties.mongoDockerImageName",
28 | matches = "^mongo:4.*"
29 | )
30 | class MongoDbDelayedMembersITTest {
31 | @Test
32 | void shouldTestDelayedMembersBecomingSecondary() {
33 | try (
34 | final MongoDbReplicaSet mongoReplicaSet = MongoDbReplicaSet.builder()
35 | .replicaSetNumber(4)
36 | .slaveDelayTimeout(50000)
37 | .slaveDelayNumber(1)
38 | .commandLineOptions(Arrays.asList("--oplogSize", "50"))
39 | .build()
40 | ) {
41 | mongoReplicaSet.start();
42 |
43 | val mongoRsUrlPrimary = mongoReplicaSet.getReplicaSetUrl();
44 | assertNotNull(mongoRsUrlPrimary);
45 | mongoReplicaSet.reconfigureReplSetToDefaults();
46 |
47 | assertThat(
48 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers()),
49 | hasItems(
50 | ReplicaSetMemberState.PRIMARY,
51 | ReplicaSetMemberState.SECONDARY,
52 | ReplicaSetMemberState.SECONDARY,
53 | ReplicaSetMemberState.SECONDARY
54 | )
55 | );
56 | }
57 | }
58 |
59 | /**
60 | * Disabled as unstable
61 | */
62 | @Test
63 | @Disabled
64 | void shouldTestDelayedMemberMightBecomeSecondary() {
65 | try (
66 | final MongoDbReplicaSet mongoReplicaSet = MongoDbReplicaSet.builder()
67 | .replicaSetNumber(4)
68 | .addToxiproxy(true)
69 | .slaveDelayTimeout(50000)
70 | .slaveDelayNumber(1)
71 | .build()
72 | ) {
73 | mongoReplicaSet.start();
74 |
75 | val mongoRsUrlPrimary = mongoReplicaSet.getReplicaSetUrl();
76 | assertNotNull(mongoRsUrlPrimary);
77 | val members = mongoReplicaSet.getMongoRsStatus().getMembers();
78 | val masterNode = mongoReplicaSet.getMasterMongoNode(members);
79 |
80 | mongoReplicaSet.disconnectNodeFromNetwork(masterNode);
81 | mongoReplicaSet.waitForMongoNodesDown(1);
82 | mongoReplicaSet.waitForMasterReelection(masterNode);
83 | mongoReplicaSet.reconfigureReplSetToDefaults();
84 |
85 | assertThat(
86 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers()),
87 | hasItems(
88 | ReplicaSetMemberState.PRIMARY,
89 | ReplicaSetMemberState.SECONDARY,
90 | ReplicaSetMemberState.SECONDARY,
91 | ReplicaSetMemberState.DOWN
92 | )
93 | );
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/api/faulttolerance/MongoDbReplicaSetDistributionITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.api.faulttolerance;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
5 | import com.github.silaev.mongodb.replicaset.model.DisconnectionType;
6 | import com.github.silaev.mongodb.replicaset.model.Pair;
7 | import com.github.silaev.mongodb.replicaset.model.ReplicaSetMemberState;
8 | import com.github.silaev.mongodb.replicaset.util.ConnectionUtils;
9 | import com.mongodb.MongoException;
10 | import com.mongodb.MongoTimeoutException;
11 | import com.mongodb.ReadPreference;
12 | import com.mongodb.WriteConcern;
13 | import com.mongodb.client.MongoClients;
14 | import com.mongodb.client.model.Filters;
15 | import lombok.val;
16 | import org.bson.Document;
17 | import org.junit.jupiter.api.function.Executable;
18 | import org.junit.jupiter.params.ParameterizedTest;
19 | import org.junit.jupiter.params.provider.ValueSource;
20 |
21 | import java.util.concurrent.TimeUnit;
22 |
23 | import static org.hamcrest.CoreMatchers.hasItems;
24 | import static org.hamcrest.MatcherAssert.assertThat;
25 | import static org.junit.jupiter.api.Assertions.assertEquals;
26 | import static org.junit.jupiter.api.Assertions.assertNotNull;
27 | import static org.junit.jupiter.api.Assertions.assertThrows;
28 |
29 | /**
30 | * Simulates two data centers scenario in which two members to Data Center 1 and one member to Data Center 2.
31 | * If one of the members of the replica set is an arbiter, distribute the arbiter to Data Center 1 with a data-bearing member.
32 | *
33 | * @author Konstantin Silaev on 1/28/2020
34 | */
35 | @IntegrationTest
36 | class MongoDbReplicaSetDistributionITTest {
37 | /**
38 | * If Data Center 1 goes down, the replica set becomes read-only.
39 | * Uses hard node disconnection via whether removing a network of a container
40 | * or cutting a connection between a mongo node and a proxy container (Toxiproxy).
41 | */
42 | @ParameterizedTest(name = "shouldTestReadOnlySecondaryAfterPrimaryAndArbiterHardOrSoftDisconnection: {index}: disconnectionType: {0}")
43 | @ValueSource(strings = {"HARD", "SOFT"})
44 | void shouldTestReadOnlySecondaryAfterPrimaryAndArbiterHardOrSoftDisconnection(
45 | final DisconnectionType disconnectionType
46 | ) {
47 | //GIVEN
48 | val mongoDbReplicaSetBuilder = MongoDbReplicaSet.builder()
49 | .replicaSetNumber(2)
50 | .addArbiter(true);
51 | if (disconnectionType == DisconnectionType.SOFT) {
52 | mongoDbReplicaSetBuilder.addToxiproxy(true);
53 | }
54 |
55 | try (
56 | final MongoDbReplicaSet mongoReplicaSet = mongoDbReplicaSetBuilder.build()
57 | ) {
58 | mongoReplicaSet.start();
59 | val mongoRsUrlPrimary = mongoReplicaSet.getReplicaSetUrl();
60 | val mongoRsUrlPrimaryPreferred = mongoReplicaSet.getReplicaSetUrl(
61 | ReadPreference.primaryPreferred().getName()
62 | );
63 | val mongoRsUrlSecondary = mongoReplicaSet.getReplicaSetUrl(
64 | ReadPreference.secondary().getName()
65 | );
66 | assertNotNull(mongoRsUrlPrimary);
67 | try (
68 | val mongoSyncClient = com.mongodb.client.MongoClients.create(
69 | ConnectionUtils.getMongoClientSettingsWithTimeout(mongoRsUrlPrimary)
70 | )
71 | ) {
72 | val docPair = Pair.of("abc", 5000);
73 | val doc = new Document(docPair.getLeft(), docPair.getRight());
74 | val dbName = "test";
75 | val collectionName = "foo";
76 | val collection = mongoSyncClient.getDatabase(dbName).getCollection(collectionName);
77 | collection.withWriteConcern(WriteConcern.MAJORITY).insertOne(doc);
78 | val replicaSetMembers = mongoReplicaSet.getMongoRsStatus().getMembers();
79 | val masterNode = mongoReplicaSet.getMasterMongoNode(replicaSetMembers);
80 | val arbiterNode = mongoReplicaSet.getArbiterMongoNode(replicaSetMembers);
81 | val filter = Filters.eq(docPair.getLeft(), docPair.getRight());
82 |
83 | //WHEN
84 | mongoReplicaSet.disconnectNodeFromNetwork(arbiterNode);
85 | mongoReplicaSet.disconnectNodeFromNetwork(masterNode);
86 |
87 | mongoReplicaSet.waitForMongoNodesDown(2);
88 |
89 | final Executable executableReadOperation = () -> collection.find(filter).first();
90 | final Executable executableWriteOperation =
91 | () -> collection.insertOne(new Document("xyz", 100));
92 |
93 | //THEN
94 | //1. Primary is the default mode. All operations read from the current replica set primary.
95 | //Therefore, reads are not possible (MongoSocketReadException/MongoSocketReadTimeoutException/MongoTimeoutException).
96 | assertThrows(MongoException.class, executableReadOperation);
97 |
98 | //2. Secondary is not writeable.
99 | assertThrows(MongoTimeoutException.class, executableWriteOperation);
100 |
101 | //3. Test read preference "primary preferred".
102 | //In most situations, operations read from the primary but if it is unavailable,
103 | //operations read from secondary members.
104 | //3.1. withReadPreference on a collection.
105 | assertEquals(doc, collection
106 | .withReadPreference(ReadPreference.primaryPreferred())
107 | .find(filter)
108 | .first()
109 | );
110 | assertEquals(doc, collection
111 | .withReadPreference(ReadPreference.secondary())
112 | .find(filter)
113 | .first()
114 | );
115 |
116 | //3.2. withReadPreference on a url connection.
117 | assertEquals(doc, MongoClients.create(mongoRsUrlPrimaryPreferred)
118 | .getDatabase(dbName).getCollection(collectionName)
119 | .find(filter).first()
120 | );
121 | assertEquals(doc, MongoClients.create(mongoRsUrlSecondary)
122 | .getDatabase(dbName).getCollection(collectionName)
123 | .find(filter).first()
124 | );
125 |
126 | //BRING ALL DISCONNECTED NODES BACK
127 | switch (disconnectionType) {
128 | case HARD:
129 | mongoReplicaSet.connectNodeToNetworkWithReconfiguration(masterNode);
130 | mongoReplicaSet.connectNodeToNetworkWithoutRemoval(arbiterNode);
131 | break;
132 | case SOFT:
133 | mongoReplicaSet.connectNodeToNetwork(masterNode);
134 | mongoReplicaSet.connectNodeToNetwork(arbiterNode);
135 | break;
136 | default:
137 | throw new IllegalArgumentException(String.format("Cannot find disconnectionType: %s", disconnectionType));
138 | }
139 | mongoReplicaSet.waitForAllMongoNodesUp();
140 | mongoReplicaSet.waitForMaster();
141 | assertThat(
142 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers()),
143 | hasItems(
144 | ReplicaSetMemberState.PRIMARY,
145 | ReplicaSetMemberState.SECONDARY,
146 | ReplicaSetMemberState.ARBITER
147 | )
148 | );
149 | }
150 | }
151 | }
152 |
153 | /**
154 | * If Data Center 2 goes down, the replica set remains writeable as the members
155 | * in Data Center 1 can hold an election.
156 | *
157 | * Uses whether hard node disconnection via removing a network of a container
158 | * or soft one via cutting a connection between a mongo node and a proxy container (Toxiproxy).
159 | */
160 | @ParameterizedTest(name = "shouldTestWriteablePrimaryAfterSecondaryHardOrSoftDisconnection: {index}: disconnectionType: {0}")
161 | @ValueSource(strings = {"HARD", "SOFT"})
162 | void shouldTestWriteablePrimaryAfterSecondaryHardOrSoftDisconnection(
163 | final DisconnectionType disconnectionType
164 | ) {
165 | //GIVEN
166 | val mongoDbReplicaSetBuilder = MongoDbReplicaSet.builder()
167 | .replicaSetNumber(2)
168 | .addArbiter(true);
169 | if (disconnectionType == DisconnectionType.SOFT) {
170 | mongoDbReplicaSetBuilder.addToxiproxy(true);
171 | }
172 | try (
173 | final MongoDbReplicaSet mongoReplicaSet = mongoDbReplicaSetBuilder.build()
174 | ) {
175 | mongoReplicaSet.start();
176 | val mongoRsUrlPrimary = mongoReplicaSet.getReplicaSetUrl();
177 | val mongoRsUrlSecondary = mongoReplicaSet.getReplicaSetUrl(
178 | ReadPreference.secondary().getName()
179 | );
180 | assertNotNull(mongoRsUrlPrimary);
181 |
182 | try (
183 | val mongoSyncClient = com.mongodb.client.MongoClients.create(
184 | ConnectionUtils.getMongoClientSettingsWithTimeout(mongoRsUrlPrimary)
185 | )
186 | ) {
187 | val docPair = Pair.of("abc", 5000);
188 | val doc = new Document(docPair.getLeft(), docPair.getRight());
189 | val dbName = "test";
190 | val collectionName = "foo";
191 | val collection = mongoSyncClient.getDatabase(dbName).getCollection(collectionName);
192 | collection.withWriteConcern(WriteConcern.MAJORITY).insertOne(doc);
193 | val replicaSetMembers = mongoReplicaSet.getMongoRsStatus().getMembers();
194 | val secondaryNode = mongoReplicaSet.getSecondaryMongoNode(replicaSetMembers);
195 | val filterDoc = Filters.eq(docPair.getLeft(), docPair.getRight());
196 | val newDocPair = Pair.of("xyz", 100);
197 | val newDoc = new Document(newDocPair.getLeft(), newDocPair.getRight());
198 | val filterNewDoc = Filters.eq(newDocPair.getLeft(), newDocPair.getRight());
199 |
200 | //WHEN
201 | mongoReplicaSet.disconnectNodeFromNetwork(secondaryNode);
202 |
203 | mongoReplicaSet.waitForMongoNodesDown(1);
204 | //mongoReplicaSet.waitForAllMongoNodesUpAllowingAnyDown();
205 |
206 | collection.insertOne(newDoc);
207 | final Executable executableReadPreferenceOnCollection = () -> collection
208 | .withReadPreference(ReadPreference.secondary())
209 | .find(filterDoc)
210 | .maxAwaitTime(5, TimeUnit.SECONDS)
211 | .maxTime(5, TimeUnit.SECONDS)
212 | .first();
213 | final Executable executableReadPreferenceUrlConnection = () ->
214 | MongoClients.create(
215 | ConnectionUtils.getMongoClientSettingsWithTimeout(mongoRsUrlSecondary)
216 | ).getDatabase(dbName).getCollection(collectionName).find(filterDoc).first();
217 |
218 | //THEN
219 | //1. read operations are supported.
220 | assertEquals(doc, collection.find(filterDoc).first());
221 | //2. write operations are supported.
222 | assertEquals(newDoc, collection.find(filterNewDoc).first());
223 |
224 | //3. secondary is unavailable.
225 | assertThrows(MongoException.class, executableReadPreferenceOnCollection);
226 | assertThrows(MongoTimeoutException.class, executableReadPreferenceUrlConnection);
227 |
228 | //BRING A DISCONNECTED NODE BACK
229 | mongoReplicaSet.connectNodeToNetwork(secondaryNode);
230 | mongoReplicaSet.waitForAllMongoNodesUp();
231 | mongoReplicaSet.waitForMaster();
232 | assertThat(
233 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers()),
234 | hasItems(
235 | ReplicaSetMemberState.PRIMARY,
236 | ReplicaSetMemberState.SECONDARY,
237 | ReplicaSetMemberState.ARBITER
238 | )
239 | );
240 | }
241 | }
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/api/faulttolerance/MongoDbReplicaSetFaultTolerancePSAApiITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.api.faulttolerance;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
5 | import com.github.silaev.mongodb.replicaset.model.HardFailureAction;
6 | import com.github.silaev.mongodb.replicaset.model.ReplicaSetMemberState;
7 | import lombok.extern.slf4j.Slf4j;
8 | import lombok.val;
9 | import org.junit.jupiter.api.AfterEach;
10 | import org.junit.jupiter.api.BeforeEach;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.params.ParameterizedTest;
13 | import org.junit.jupiter.params.provider.ValueSource;
14 |
15 | import static org.hamcrest.CoreMatchers.hasItems;
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 | import static org.junit.jupiter.api.Assertions.assertEquals;
18 |
19 | /**
20 | * A fault tolerance tests for Primary with a Secondary and an Arbiter (PSA)
21 | */
22 | @IntegrationTest
23 | @Slf4j
24 | class MongoDbReplicaSetFaultTolerancePSAApiITTest {
25 | private static final int REPLICA_SET_NUMBER = 2;
26 |
27 | private final MongoDbReplicaSet mongoReplicaSet = MongoDbReplicaSet.builder()
28 | .replicaSetNumber(REPLICA_SET_NUMBER)
29 | .addArbiter(true)
30 | .build();
31 |
32 | @BeforeEach
33 | void setUp() {
34 | mongoReplicaSet.start();
35 | }
36 |
37 | @AfterEach
38 | void tearDown() {
39 | mongoReplicaSet.stop();
40 | }
41 |
42 | @ParameterizedTest(name = "{index}: action: {0}")
43 | @ValueSource(strings = {"KILL", "STOP"})
44 | void shouldTestFailoverBecauseOfContainerIsKilledOrStopped(
45 | final HardFailureAction action
46 | ) {
47 | //===STAGE 1: killing or stopping a master node.
48 | //GIVEN
49 | val mongoNodes = mongoReplicaSet.getMongoRsStatus().getMembers();
50 | val nodeStatesBeforeFailure = mongoReplicaSet.nodeStates(mongoNodes);
51 | val currentMasterNode = mongoReplicaSet.getMasterMongoNode(mongoNodes);
52 |
53 | //WHEN: Kill or stop the master node.
54 | switch (action) {
55 | case KILL:
56 | mongoReplicaSet.killNode(currentMasterNode);
57 | break;
58 | case STOP:
59 | mongoReplicaSet.stopNode(currentMasterNode);
60 | break;
61 | default:
62 | throw new IllegalArgumentException(String.format("Cannot find action: %s", action));
63 | }
64 |
65 | //THEN
66 | //Check the state of the members before the failure.
67 | assertThat(
68 | nodeStatesBeforeFailure,
69 | hasItems(
70 | ReplicaSetMemberState.PRIMARY,
71 | ReplicaSetMemberState.SECONDARY,
72 | ReplicaSetMemberState.ARBITER
73 | )
74 | );
75 | assertEquals(REPLICA_SET_NUMBER + 1, nodeStatesBeforeFailure.size());
76 |
77 | //===STAGE 2: Surviving a failure.
78 | //WHEN: Wait for reelection.
79 | mongoReplicaSet.waitForMasterReelection(currentMasterNode);
80 | mongoReplicaSet.removeNodeFromReplSetConfigWithForce(currentMasterNode);
81 | val actualNodeStatesAfterElection =
82 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers());
83 |
84 | //THEN: Check the state of the members when election is over.
85 | assertThat(
86 | actualNodeStatesAfterElection,
87 | hasItems(
88 | ReplicaSetMemberState.PRIMARY,
89 | ReplicaSetMemberState.ARBITER
90 | )
91 | );
92 | assertEquals(REPLICA_SET_NUMBER, actualNodeStatesAfterElection.size());
93 | }
94 |
95 | @Test
96 | void shouldTestFailoverBecauseOfContainerNetworkDisconnect() {
97 | //===STAGE 1: Disconnecting a master node from its network.
98 | //GIVEN
99 | val mongoNodes = mongoReplicaSet.getMongoRsStatus().getMembers();
100 | val nodeStatesBeforeFailure = mongoReplicaSet.nodeStates(mongoNodes);
101 | val currentMasterNode = mongoReplicaSet.getMasterMongoNode(mongoNodes);
102 |
103 | //WHEN: Disconnect a master node from its network.
104 | mongoReplicaSet.disconnectNodeFromNetwork(currentMasterNode);
105 |
106 | //THEN
107 | //Check the state of members before the failure.
108 | assertThat(
109 | nodeStatesBeforeFailure,
110 | hasItems(
111 | ReplicaSetMemberState.PRIMARY,
112 | ReplicaSetMemberState.SECONDARY,
113 | ReplicaSetMemberState.ARBITER
114 | )
115 | );
116 | assertEquals(REPLICA_SET_NUMBER + 1, nodeStatesBeforeFailure.size());
117 |
118 | //===STAGE 2: Surviving a failure.
119 | //WHEN: Wait for reelection.
120 | mongoReplicaSet.waitForMasterReelection(currentMasterNode);
121 |
122 | val actualNodeStatesAfterElection =
123 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers());
124 |
125 | //THEN: Check the state of members when election is over.
126 | assertThat(
127 | actualNodeStatesAfterElection,
128 | hasItems(
129 | ReplicaSetMemberState.PRIMARY,
130 | ReplicaSetMemberState.DOWN,
131 | ReplicaSetMemberState.ARBITER
132 | )
133 | );
134 | assertEquals(REPLICA_SET_NUMBER + 1, actualNodeStatesAfterElection.size());
135 |
136 | //===STAGE 3: Connecting a disconnected node back.
137 | //WHEN: Connect back.
138 | mongoReplicaSet.connectNodeToNetworkWithForceRemoval(currentMasterNode);
139 | mongoReplicaSet.waitForAllMongoNodesUp();
140 | val actualNodeStatesAfterConnectingBack =
141 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers());
142 |
143 | //THEN
144 | assertThat(
145 | actualNodeStatesAfterConnectingBack,
146 | hasItems(
147 | ReplicaSetMemberState.PRIMARY,
148 | ReplicaSetMemberState.ARBITER,
149 | ReplicaSetMemberState.SECONDARY
150 | )
151 | );
152 | assertEquals(REPLICA_SET_NUMBER + 1, actualNodeStatesAfterConnectingBack.size());
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/api/faulttolerance/MongoDbReplicaSetFaultTolerancePSSApiITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.api.faulttolerance;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
5 | import com.github.silaev.mongodb.replicaset.model.HardFailureAction;
6 | import com.github.silaev.mongodb.replicaset.model.ReplicaSetMemberState;
7 | import lombok.extern.slf4j.Slf4j;
8 | import lombok.val;
9 | import org.junit.jupiter.api.AfterEach;
10 | import org.junit.jupiter.api.BeforeEach;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.params.ParameterizedTest;
13 | import org.junit.jupiter.params.provider.ValueSource;
14 |
15 | import static org.hamcrest.CoreMatchers.hasItems;
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 | import static org.junit.jupiter.api.Assertions.assertEquals;
18 |
19 | /**
20 | * A fault tolerance tests for Primary with Two Secondary Members (P-S-S)
21 | */
22 | @IntegrationTest
23 | @Slf4j
24 | class MongoDbReplicaSetFaultTolerancePSSApiITTest {
25 | private static final int REPLICA_SET_NUMBER = 3;
26 |
27 | private final MongoDbReplicaSet mongoReplicaSet = MongoDbReplicaSet.builder()
28 | .replicaSetNumber(REPLICA_SET_NUMBER)
29 | .build();
30 |
31 | @BeforeEach
32 | void setUp() {
33 | mongoReplicaSet.start();
34 | }
35 |
36 | @AfterEach
37 | void tearDown() {
38 | mongoReplicaSet.stop();
39 | }
40 |
41 | @ParameterizedTest(name = "{index}: action: {0}")
42 | @ValueSource(strings = {"KILL", "STOP"})
43 | void shouldTestFailoverBecauseOfContainerIsKilledOrStopped(
44 | final HardFailureAction action
45 | ) {
46 | //===STAGE 1: killing or stopping a master node.
47 | //GIVEN
48 | val mongoNodes = mongoReplicaSet.getMongoRsStatus().getMembers();
49 | val nodeStatesBeforeFailover = mongoReplicaSet.nodeStates(mongoNodes);
50 | val currentMasterNode = mongoReplicaSet.getMasterMongoNode(mongoNodes);
51 |
52 | //WHEN: Kill or stop the master node.
53 | switch (action) {
54 | case KILL:
55 | mongoReplicaSet.killNode(currentMasterNode);
56 | break;
57 | case STOP:
58 | mongoReplicaSet.stopNode(currentMasterNode);
59 | break;
60 | default:
61 | throw new IllegalArgumentException(String.format("Cannot find action: %s", action));
62 | }
63 |
64 | //THEN
65 | //1. Check the state of members before a failover.
66 | assertThat(
67 | nodeStatesBeforeFailover,
68 | hasItems(
69 | ReplicaSetMemberState.PRIMARY,
70 | ReplicaSetMemberState.SECONDARY,
71 | ReplicaSetMemberState.SECONDARY
72 | )
73 | );
74 | assertEquals(REPLICA_SET_NUMBER, nodeStatesBeforeFailover.size());
75 |
76 | //===STAGE 2: Surviving a failure.
77 | //WHEN: Wait for reelection.
78 | mongoReplicaSet.waitForMasterReelection(currentMasterNode);
79 | mongoReplicaSet.removeNodeFromReplSetConfigWithForce(currentMasterNode);
80 | val actualNodeStatesAfterElection =
81 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers());
82 |
83 | //THEN: Check the state of members when election is over.
84 | assertThat(
85 | actualNodeStatesAfterElection,
86 | hasItems(
87 | ReplicaSetMemberState.PRIMARY,
88 | ReplicaSetMemberState.SECONDARY
89 | )
90 | );
91 | assertEquals(REPLICA_SET_NUMBER - 1, actualNodeStatesAfterElection.size());
92 | }
93 |
94 | @Test
95 | void shouldTestFailoverBecauseOfContainerNetworkDisconnect() {
96 | //===STAGE 1: Disconnecting a master node from its network.
97 | //GIVEN
98 | val mongoNodes = mongoReplicaSet.getMongoRsStatus().getMembers();
99 | val nodeStatesBeforeFailover = mongoReplicaSet.nodeStates(mongoNodes);
100 | val currentMasterNode = mongoReplicaSet.getMasterMongoNode(mongoNodes);
101 |
102 | //WHEN: Disconnect a master node from its network.
103 | mongoReplicaSet.disconnectNodeFromNetwork(currentMasterNode);
104 |
105 | //THEN
106 | //1. Check the state of members before the failure.
107 | assertThat(
108 | nodeStatesBeforeFailover,
109 | hasItems(
110 | ReplicaSetMemberState.PRIMARY,
111 | ReplicaSetMemberState.SECONDARY,
112 | ReplicaSetMemberState.SECONDARY
113 | )
114 | );
115 | assertEquals(REPLICA_SET_NUMBER, nodeStatesBeforeFailover.size());
116 |
117 | //===STAGE 2: Surviving a failure.
118 | //WHEN: Wait for reelection.
119 | mongoReplicaSet.waitForMasterReelection(currentMasterNode);
120 | val actualNodeStatesAfterElection =
121 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers());
122 |
123 | //THEN: Check the state of members when election is over.
124 | assertThat(
125 | actualNodeStatesAfterElection,
126 | hasItems(
127 | ReplicaSetMemberState.PRIMARY,
128 | ReplicaSetMemberState.DOWN,
129 | ReplicaSetMemberState.SECONDARY
130 | )
131 | );
132 | assertEquals(REPLICA_SET_NUMBER, actualNodeStatesAfterElection.size());
133 |
134 | //===STAGE 3: Connecting a disconnected node back.
135 | //WHEN: Connect back.
136 | mongoReplicaSet.connectNodeToNetwork(currentMasterNode);
137 | mongoReplicaSet.waitForAllMongoNodesUp();
138 | val actualNodeStatesAfterConnectingBack =
139 | mongoReplicaSet.nodeStates(mongoReplicaSet.getMongoRsStatus().getMembers());
140 |
141 | //THEN
142 | assertThat(
143 | actualNodeStatesAfterConnectingBack,
144 | hasItems(
145 | ReplicaSetMemberState.PRIMARY,
146 | ReplicaSetMemberState.SECONDARY,
147 | ReplicaSetMemberState.SECONDARY
148 | )
149 | );
150 | assertEquals(REPLICA_SET_NUMBER, actualNodeStatesAfterConnectingBack.size());
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/transaction/BaseMongoDbReplicaSetTransactionITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.transaction;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.util.ConnectionUtils;
5 | import com.mongodb.ReadConcern;
6 | import com.mongodb.ReadPreference;
7 | import com.mongodb.TransactionOptions;
8 | import com.mongodb.WriteConcern;
9 | import com.mongodb.client.TransactionBody;
10 | import lombok.val;
11 | import org.bson.Document;
12 |
13 | import static org.junit.jupiter.api.Assertions.assertEquals;
14 | import static org.junit.jupiter.api.Assertions.assertNotNull;
15 |
16 | abstract class BaseMongoDbReplicaSetTransactionITTest {
17 | /**
18 | * Taken from https://docs.mongodb.com
19 | */
20 | void shouldExecuteTransactions(final MongoDbReplicaSet replicaSet) {
21 | //GIVEN
22 | val mongoRsUrl = replicaSet.getReplicaSetUrl();
23 | assertNotNull(mongoRsUrl);
24 | try (
25 | val mongoSyncClient = com.mongodb.client.MongoClients.create(
26 | ConnectionUtils.getMongoClientSettingsWithTimeout(mongoRsUrl)
27 | )
28 | ) {
29 | mongoSyncClient.getDatabase("mydb1").getCollection("foo")
30 | .withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("abc", 0));
31 | mongoSyncClient.getDatabase("mydb2").getCollection("bar")
32 | .withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("xyz", 0));
33 |
34 | try (val clientSession = mongoSyncClient.startSession()) {
35 | val txnOptions = TransactionOptions.builder()
36 | .readPreference(ReadPreference.primary())
37 | .readConcern(ReadConcern.LOCAL)
38 | .writeConcern(WriteConcern.MAJORITY)
39 | .build();
40 |
41 | val trxResult = "Inserted into collections in different databases";
42 |
43 | //WHEN + THEN
44 | TransactionBody txnBody = () -> {
45 | val coll1 = mongoSyncClient.getDatabase("mydb1").getCollection("foo");
46 | val coll2 = mongoSyncClient.getDatabase("mydb2").getCollection("bar");
47 |
48 | coll1.insertOne(clientSession, new Document("abc", 1));
49 | coll2.insertOne(clientSession, new Document("xyz", 999));
50 | return trxResult;
51 | };
52 |
53 | val trxResultActual = clientSession.withTransaction(txnBody, txnOptions);
54 | assertEquals(trxResult, trxResultActual);
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/transaction/MongoDbReplicaSetTransactionMultiNodeITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.transaction;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.EnabledIfSystemPropertyEnabledByDefault;
5 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
6 | import org.junit.jupiter.api.AfterAll;
7 | import org.junit.jupiter.api.BeforeAll;
8 | import org.junit.jupiter.api.Test;
9 |
10 | @IntegrationTest
11 | @EnabledIfSystemPropertyEnabledByDefault(
12 | named = "mongoReplicaSetProperties.mongoDockerImageName",
13 | matches = "^mongo:4.*|^mongo:5.*"
14 | )
15 | class MongoDbReplicaSetTransactionMultiNodeITTest extends
16 | BaseMongoDbReplicaSetTransactionITTest {
17 | private static final int REPLICA_SET_NUMBER = 3;
18 |
19 | private static final MongoDbReplicaSet MONGO_REPLICA_SET = MongoDbReplicaSet.builder()
20 | .replicaSetNumber(REPLICA_SET_NUMBER)
21 | .build();
22 |
23 | @BeforeAll
24 | static void setUpAll() {
25 | MONGO_REPLICA_SET.start();
26 | }
27 |
28 | @AfterAll
29 | static void tearDownAll() {
30 | MONGO_REPLICA_SET.stop();
31 | }
32 |
33 | @Test
34 | void shouldExecuteTransactions() {
35 | super.shouldExecuteTransactions(MONGO_REPLICA_SET);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/integration/transaction/MongoDbReplicaSetTransactionSingleNodeITTest.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.integration.transaction;
2 |
3 | import com.github.silaev.mongodb.replicaset.MongoDbReplicaSet;
4 | import com.github.silaev.mongodb.replicaset.core.EnabledIfSystemPropertyEnabledByDefault;
5 | import com.github.silaev.mongodb.replicaset.core.IntegrationTest;
6 | import org.junit.jupiter.api.AfterAll;
7 | import org.junit.jupiter.api.BeforeAll;
8 | import org.junit.jupiter.api.Test;
9 |
10 | @IntegrationTest
11 | @EnabledIfSystemPropertyEnabledByDefault(
12 | named = "mongoReplicaSetProperties.mongoDockerImageName",
13 | matches = "^mongo:4.*|^mongo:5.*"
14 | )
15 | class MongoDbReplicaSetTransactionSingleNodeITTest extends
16 | BaseMongoDbReplicaSetTransactionITTest {
17 | private static final int REPLICA_SET_NUMBER = 1;
18 |
19 | private static final MongoDbReplicaSet MONGO_REPLICA_SET = MongoDbReplicaSet.builder()
20 | .replicaSetNumber(REPLICA_SET_NUMBER)
21 | .build();
22 |
23 | @BeforeAll
24 | static void setUpAll() {
25 | MONGO_REPLICA_SET.start();
26 | }
27 |
28 | @AfterAll
29 | static void tearDownAll() {
30 | MONGO_REPLICA_SET.stop();
31 | }
32 |
33 | @Test
34 | void shouldExecuteTransactions() {
35 | super.shouldExecuteTransactions(MONGO_REPLICA_SET);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/model/DisconnectionType.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | /**
4 | * @author Konstantin Silaev on 3/10/2020
5 | */
6 | public enum DisconnectionType {
7 | /**
8 | * Removing a network of a container.
9 | */
10 | HARD,
11 | /**
12 | * Cutting a connection between a mongo node and a proxy container (Toxiproxy).
13 | */
14 | SOFT
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/model/HardFailureAction.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.model;
2 |
3 | /**
4 | * @author Konstantin Silaev on 3/10/2020
5 | */
6 | public enum HardFailureAction {
7 | KILL, STOP
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/util/CollectionUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.util;
2 |
3 | import com.mongodb.reactivestreams.client.MongoClient;
4 | import com.mongodb.reactivestreams.client.MongoCollection;
5 | import org.bson.Document;
6 |
7 | import java.util.Collection;
8 | import java.util.HashSet;
9 | import java.util.Objects;
10 | import java.util.Set;
11 |
12 | public class CollectionUtils {
13 |
14 | private CollectionUtils() {
15 |
16 | }
17 |
18 | public static boolean isEqualCollection(final Collection> a,
19 | final Collection> b) {
20 | Objects.requireNonNull(a);
21 | Objects.requireNonNull(b);
22 | Set> set1 = new HashSet<>(a);
23 | Set> set2 = new HashSet<>(b);
24 |
25 | return a.size() == b.size() && set1.equals(set2);
26 | }
27 |
28 | public static MongoCollection getCollection(
29 | final MongoClient mongoClient,
30 | final String dbName, final String collectionName
31 | ) {
32 | return mongoClient.getDatabase(dbName).getCollection(collectionName);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/util/ConnectionUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.util;
2 |
3 | import com.mongodb.ConnectionString;
4 | import com.mongodb.MongoClientSettings;
5 | import com.mongodb.WriteConcern;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import java.util.concurrent.TimeUnit;
9 |
10 | /**
11 | * @author Konstantin Silaev on 3/19/2020
12 | */
13 | public class ConnectionUtils {
14 | private ConnectionUtils() {
15 | }
16 |
17 | /**
18 | * Used for setting timeouts to fail-fast behaviour.
19 | *
20 | * @param mongoRsUrlPrimary a connection string
21 | * @return MongoClientSettings with timeouts set
22 | */
23 | @NotNull
24 | public static MongoClientSettings getMongoClientSettingsWithTimeout(
25 | final String mongoRsUrlPrimary,
26 | final WriteConcern writeConcern,
27 | final int timeout
28 | ) {
29 | final ConnectionString connectionString = new ConnectionString(mongoRsUrlPrimary);
30 | return MongoClientSettings.builder()
31 | .writeConcern(writeConcern.withWTimeout(timeout, TimeUnit.SECONDS))
32 | .applyToClusterSettings(c -> c.serverSelectionTimeout(timeout, TimeUnit.SECONDS))
33 | .applyConnectionString(connectionString)
34 | .applyToSocketSettings(
35 | b -> b
36 | .readTimeout(timeout, TimeUnit.SECONDS)
37 | .connectTimeout(timeout, TimeUnit.SECONDS)
38 | ).build();
39 | }
40 |
41 | @NotNull
42 | public static MongoClientSettings getMongoClientSettingsWithTimeout(
43 | final String mongoRsUrlPrimary
44 | ) {
45 | return getMongoClientSettingsWithTimeout(mongoRsUrlPrimary, WriteConcern.W1, 5);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/com/github/silaev/mongodb/replicaset/util/SubscriberHelperUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.silaev.mongodb.replicaset.util;
2 |
3 | import com.mongodb.MongoTimeoutException;
4 | import lombok.SneakyThrows;
5 | import lombok.extern.slf4j.Slf4j;
6 | import lombok.val;
7 | import org.bson.Document;
8 | import org.reactivestreams.Publisher;
9 | import org.reactivestreams.Subscriber;
10 | import org.reactivestreams.Subscription;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.concurrent.CountDownLatch;
15 | import java.util.concurrent.TimeUnit;
16 |
17 | import static java.lang.String.format;
18 |
19 | /**
20 | * Subscriber helper utility class taken from:
21 | *
22 | * @see https://github.com/mongodb/mongo-java-driver-reactivestreams
23 | */
24 | @Slf4j
25 | public final class SubscriberHelperUtils {
26 | private SubscriberHelperUtils() {
27 | }
28 |
29 | @SneakyThrows
30 | public static SubscriberHelperUtils.PrintDocumentSubscriber getSubscriber(
31 | final Publisher command
32 | ) {
33 | val subscriber = new SubscriberHelperUtils.PrintDocumentSubscriber();
34 | command.subscribe(subscriber);
35 | subscriber.await();
36 | return subscriber;
37 | }
38 |
39 | /**
40 | * A Subscriber that stores the publishers results and provides a latch so can block on completion.
41 | *
42 | * @param The publishers result type
43 | */
44 | public static class ObservableSubscriber implements Subscriber {
45 | private final List received;
46 | private final List errors;
47 | private final CountDownLatch latch;
48 | private volatile Subscription subscription;
49 | private volatile boolean completed;
50 |
51 | ObservableSubscriber() {
52 | this.received = new ArrayList<>();
53 | this.errors = new ArrayList<>();
54 | this.latch = new CountDownLatch(1);
55 | }
56 |
57 | @Override
58 | public void onSubscribe(final Subscription s) {
59 | subscription = s;
60 | }
61 |
62 | @Override
63 | public void onNext(final T t) {
64 | received.add(t);
65 | }
66 |
67 | @Override
68 | public void onError(final Throwable t) {
69 | log.error(t.getMessage());
70 | errors.add(t);
71 | onComplete();
72 | }
73 |
74 | @Override
75 | public void onComplete() {
76 | completed = true;
77 | latch.countDown();
78 | }
79 |
80 | public Subscription getSubscription() {
81 | return subscription;
82 | }
83 |
84 | public List getReceived() {
85 | return received;
86 | }
87 |
88 | public Throwable getError() {
89 | if (errors.size() > 0) {
90 | return errors.get(0);
91 | }
92 | return null;
93 | }
94 |
95 | public boolean isCompleted() {
96 | return completed;
97 | }
98 |
99 | public List get(final long timeout, final TimeUnit unit) throws Throwable {
100 | return await(timeout, unit).getReceived();
101 | }
102 |
103 | public ObservableSubscriber await() throws Throwable {
104 | return await(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
105 | }
106 |
107 | public ObservableSubscriber await(final long timeout, final TimeUnit unit) throws Throwable {
108 | subscription.request(Integer.MAX_VALUE);
109 | if (!latch.await(timeout, unit)) {
110 | throw new MongoTimeoutException("Publisher onComplete timed out");
111 | }
112 | if (!errors.isEmpty()) {
113 | throw errors.get(0);
114 | }
115 | return this;
116 | }
117 | }
118 |
119 | /**
120 | * A Subscriber that immediately requests Integer.MAX_VALUE onSubscribe
121 | *
122 | * @param The publishers result type
123 | */
124 | public static class OperationSubscriber extends ObservableSubscriber {
125 |
126 | @Override
127 | public void onSubscribe(final Subscription s) {
128 | super.onSubscribe(s);
129 | s.request(Integer.MAX_VALUE);
130 | }
131 | }
132 |
133 | /**
134 | * A Subscriber that prints a message including the received items on completion
135 | *
136 | * @param The publishers result type
137 | */
138 | public static class PrintSubscriber extends OperationSubscriber {
139 | private final String message;
140 |
141 | /**
142 | * A Subscriber that outputs a message onComplete.
143 | *
144 | * @param message the message to output onComplete
145 | */
146 | public PrintSubscriber(final String message) {
147 | this.message = message;
148 | }
149 |
150 | @Override
151 | public void onComplete() {
152 | log.debug(format(message, getReceived()));
153 | super.onComplete();
154 | }
155 | }
156 |
157 | /**
158 | * A Subscriber that prints the json version of each document
159 | */
160 | public static class PrintDocumentSubscriber extends OperationSubscriber {
161 | @Override
162 | public void onNext(final Document document) {
163 | super.onNext(document);
164 | log.debug(document.toJson());
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/test/resources/enabled-false.yml:
--------------------------------------------------------------------------------
1 | mongoReplicaSetProperties:
2 | enabled: false
3 | mongoDockerImageName: mongo:4.1.13
4 |
--------------------------------------------------------------------------------
/src/test/resources/enabled-true.yml:
--------------------------------------------------------------------------------
1 | mongoReplicaSetProperties:
2 | enabled: true
3 |
--------------------------------------------------------------------------------
/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | %d{HH:mm:ss.SSS} %-5level %logger - %msg%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:
--------------------------------------------------------------------------------
1 | mock-maker-inline
--------------------------------------------------------------------------------
/src/test/resources/shell-output/rs-status-double-primaries.txt:
--------------------------------------------------------------------------------
1 | MongoDB shell version v4.4.3
2 |
3 | connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
4 |
5 | Implicit session: session { "id" : UUID("fcf16368-ad95-4a7c-b1d0-bcf3741fbcbb") }
6 |
7 | MongoDB server version: 4.4.3
8 |
9 | {
10 | "set" : "docker-rs",
11 | "date" : ISODate("2021-02-11T08:03:08.109Z"),
12 | "myState" : 1,
13 | "term" : NumberLong(2),
14 | "syncSourceHost" : "",
15 | "syncSourceId" : -1,
16 | "heartbeatIntervalMillis" : NumberLong(2000),
17 | "majorityVoteCount" : 2,
18 | "writeMajorityCount" : 2,
19 | "votingMembersCount" : 3,
20 | "writableVotingMembersCount" : 3,
21 | "optimes" : {
22 | "lastCommittedOpTime" : {
23 | "ts" : Timestamp(1613030575, 6),
24 | "t" : NumberLong(1)
25 | },
26 | "lastCommittedWallTime" : ISODate("2021-02-11T08:02:55.711Z"),
27 | "readConcernMajorityOpTime" : {
28 | "ts" : Timestamp(1613030575, 6),
29 | "t" : NumberLong(1)
30 | },
31 | "readConcernMajorityWallTime" : ISODate("2021-02-11T08:02:55.711Z"),
32 | "appliedOpTime" : {
33 | "ts" : Timestamp(1613030576, 1),
34 | "t" : NumberLong(1)
35 | },
36 | "durableOpTime" : {
37 | "ts" : Timestamp(1613030576, 1),
38 | "t" : NumberLong(1)
39 | },
40 | "lastAppliedWallTime" : ISODate("2021-02-11T08:02:56.759Z"),
41 | "lastDurableWallTime" : ISODate("2021-02-11T08:02:56.759Z")
42 | },
43 | "lastStableRecoveryTimestamp" : Timestamp(1613030575, 6),
44 | "e
45 | lectionCandidateMetrics" : {
46 | "lastElectionReason" : "electionTimeout",
47 | "lastElectionDate" : ISODate("2021-02-11T08:03:07.531Z"),
48 | "electionTerm" : NumberLong(2),
49 | "lastCommittedOpTimeAtElection" : {
50 | "ts" : Timestamp(1613030575, 6),
51 | "t" : NumberLong(1)
52 | },
53 | "lastSeenOpTimeAtElection" : {
54 | "ts" : Timestamp(1613030576, 1),
55 | "t" : NumberLong(1)
56 | },
57 | "numVotesNeeded" : 2,
58 | "priorityAtElection" : 1,
59 | "electionTimeoutMillis" : NumberLong(10000),
60 | "priorPrimaryMemberId" : 0
61 | },
62 | "electionParticipantMetrics" : {
63 | "votedForCandidate" : true,
64 | "electionTerm" : NumberLong(1),
65 | "lastVoteDate" : ISODate("2021-02-11T08:02:55.636Z"),
66 | "electionCandidateMemberId" : 0,
67 | "voteReason" : "",
68 | "lastAppliedOpTimeAtElection" : {
69 | "ts" : Timestamp(1613030564, 1),
70 | "t" : NumberLong(-1)
71 | },
72 | "maxAppliedOpTimeInSet" : {
73 | "ts" : Timestamp(1613030564, 1),
74 | "t" : NumberLong(-1)
75 | },
76 | "priorityAtElection" : 1
77 | },
78 | "members" : [
79 | {
80 | "_id" : 0,
81 | "name" : "dockerhost:49857",
82 | "health" : 1,
83 | "sta
84 | te" : 1,
85 | "stateStr" : "PRIMARY",
86 | "uptime" : 22,
87 | "optime" : {
88 | "ts" : Timestamp(1613030575, 6),
89 | "t" : NumberLong(1)
90 | },
91 | "optimeDurable" : {
92 | "ts" : Timestamp(1613030575, 6),
93 | "t" : NumberLong(1)
94 | },
95 | "optimeDate" : ISODate("2021-02-11T08:02:55Z"),
96 | "optimeDurableDate" : ISODate("2021-02-11T08:02:55Z"),
97 | "lastHeartbeat" : ISODate("2021-02-11T08:02:56.708Z"),
98 | "lastHeartbeatRecv" : ISODate("2021-02-11T08:03:07.676Z"),
99 | "pingMs" : NumberLong(2),
100 | "lastHeartbeatMessage" : "",
101 | "syncSourceHost" : "",
102 | "syncSourceId" : -1,
103 | "infoMessage" : "",
104 | "electionTime" : Timestamp(1613030575, 1),
105 | "electionDate" : ISODate("2021-02-11T08:02:55Z"),
106 | "configVersion" : 1,
107 | "configTerm" : 1
108 | },
109 | {
110 | "_id" : 1,
111 | "name" : "dockerhost:49858",
112 | "health" : 1,
113 | "state" : 1,
114 | "stateStr" : "PRIMARY",
115 | "uptime" : 27,
116 | "optime" : {
117 | "ts" : Timestamp(1613030576, 1),
118 | "t" : NumberLong(1)
119 | },
120 | "optimeDate" : ISODate("2021-02-11T08:02:56Z"),
121 | "syncSourceHost" : ""
122 | ,
123 | "syncSourceId" : -1,
124 | "infoMessage" : "",
125 | "electionTime" : Timestamp(1613030587, 1),
126 | "electionDate" : ISODate("2021-02-11T08:03:07Z"),
127 | "configVersion" : 1,
128 | "configTerm" : 1,
129 | "self" : true,
130 | "lastHeartbeatMessage" : ""
131 | },
132 | {
133 | "_id" : 2,
134 | "name" : "dockerhost:49859",
135 | "health" : 1,
136 | "state" : 2,
137 | "stateStr" : "SECONDARY",
138 | "uptime" : 22,
139 | "optime" : {
140 | "ts" : Timestamp(1613030576, 1),
141 | "t" : NumberLong(1)
142 | },
143 | "optimeDurable" : {
144 | "ts" : Timestamp(1613030576, 1),
145 | "t" : NumberLong(1)
146 | },
147 | "optimeDate" : ISODate("2021-02-11T08:02:56Z"),
148 | "optimeDurableDate" : ISODate("2021-02-11T08:02:56Z"),
149 | "lastHeartbeat" : ISODate("2021-02-11T08:03:07.542Z"),
150 | "lastHeartbeatRecv" : ISODate("2021-02-11T08:03:06.726Z"),
151 | "pingMs" : NumberLong(1),
152 | "lastHeartbeatMessage" : "",
153 | "syncSourceHost" : "dockerhost:49857",
154 | "syncSourceId" : 0,
155 | "infoMessage" : "",
156 | "configVersion" : 1,
157 | "configTerm" : 1
158 | }
159 | ],
160 | "ok" : 1,
161 | "$clusterTime" : {
162 | "clusterTim
163 | e" : Timestamp(1613030587, 1),
164 | "signature" : {
165 | "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
166 | "keyId" : NumberLong(0)
167 | }
168 | },
169 | "operationTime" : Timestamp(1613030576, 1)
170 | }
171 |
--------------------------------------------------------------------------------
/src/test/resources/shell-output/rs-status-framed.txt:
--------------------------------------------------------------------------------
1 | MongoDB shell version v4.4.3
2 |
3 | connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
4 |
5 | Implicit session: session { "id" : UUID("54102cb3-a263-4627-859d-cac49d342eed") }
6 |
7 | MongoDB server version: 4.4.3
8 |
9 | {
10 | "set" : "docker-rs",
11 | "date" : ISODate("2021-01-10T10:35:26.229Z"),
12 | "myState" : 1,
13 | "term" : NumberLong(2),
14 | "syncSourceHost" : "",
15 | "syncSourceId" : -1,
16 | "heartbeatIntervalMillis" : NumberLong(2000),
17 | "majorityVoteCount" : 2,
18 | "writeMajorityCount" : 2,
19 | "votingMembersCount" : 3,
20 | "writableVotingMembersCount" : 3,
21 | "optimes" : {
22 | "lastCommittedOpTime" : {
23 | "ts" : Timestamp(1610274923, 1),
24 | "t" : NumberLong(2)
25 | },
26 | "lastCommittedWallTime" : ISODate("2021-01-10T10:35:23.950Z"),
27 | "readConcernMajorityOpTime" : {
28 | "ts" : Timestamp(1610274923, 1),
29 | "t" : NumberLong(2)
30 | },
31 | "readConcernMajorityWallTime" : ISODate("2021-01-10T10:35:23.950Z"),
32 | "appliedOpTime" : {
33 | "ts" : Timestamp(1610274923, 1),
34 | "t" : NumberLong(2)
35 | },
36 | "durableOpTime" : {
37 | "ts" : Timestamp(1610274923, 1),
38 | "t" : NumberLong(2)
39 | },
40 | "lastAppliedWallTime" : ISODate("2021-01-10T10:35:23.950Z"),
41 | "lastDurableWallTime" : ISODate("2021-01-10T10:35:23.950Z")
42 | },
43 | "lastStableRecoveryTimestamp" : Timestamp(1610274908, 6),
44 | "e
45 | lectionCandidateMetrics" : {
46 | "lastElectionReason" : "electionTimeout",
47 | "lastElectionDate" : ISODate("2021-01-10T10:35:19.773Z"),
48 | "electionTerm" : NumberLong(2),
49 | "lastCommittedOpTimeAtElection" : {
50 | "ts" : Timestamp(1610274908, 6),
51 | "t" : NumberLong(1)
52 | },
53 | "lastSeenOpTimeAtElection" : {
54 | "ts" : Timestamp(1610274908, 7),
55 | "t" : NumberLong(1)
56 | },
57 | "numVotesNeeded" : 2,
58 | "priorityAtElection" : 1,
59 | "electionTimeoutMillis" : NumberLong(10000),
60 | "numCatchUpOps" : NumberLong(0),
61 | "newTermStartDate" : ISODate("2021-01-10T10:35:19.785Z"),
62 | "wMajorityWriteAvailabilityDate" : ISODate("2021-01-10T10:35:20.016Z")
63 | },
64 | "electionParticipantMetrics" : {
65 | "votedForCandidate" : true,
66 | "electionTerm" : NumberLong(1),
67 | "lastVoteDate" : ISODate("2021-01-10T10:35:08.056Z"),
68 | "electionCandidateMemberId" : 0,
69 | "voteReason" : "",
70 | "lastAppliedOpTimeAtElection" : {
71 | "ts" : Timestamp(1610274897, 1),
72 | "t" : NumberLong(-1)
73 | },
74 | "maxAppliedOpTimeInSet" : {
75 | "ts" : Timestamp(1610274897, 1),
76 | "t" : N
77 | umberLong(-1)
78 | },
79 | "priorityAtElection" : 1
80 | },
81 | "members" : [
82 | {
83 | "_id" : 1,
84 | "name" : "dockerhost:56023",
85 | "health" : 1,
86 | "state" : 1,
87 | "stateStr" : "PRIMARY",
88 | "uptime" : 31,
89 | "optime" : {
90 | "ts" : Timestamp(1610274923, 1),
91 | "t" : NumberLong(2)
92 | },
93 | "optimeDate" : ISODate("2021-01-10T10:35:23Z"),
94 | "syncSourceHost" : "",
95 | "syncSourceId" : -1,
96 | "infoMessage" : "",
97 | "electionTime" : Timestamp(1610274919, 1),
98 | "electionDate" : ISODate("2021-01-10T10:35:19Z"),
99 | "configVersion" : 3,
100 | "configTerm" : 2,
101 | "self" : true,
102 | "lastHeartbeatMessage" : ""
103 | },
104 | {
105 | "_id" : 2,
106 | "name" : "dockerhost:56024",
107 | "health" : 1,
108 | "state" : 2,
109 | "stateStr" : "SECONDARY",
110 | "uptime" : 28,
111 | "optime" : {
112 | "ts" : Timestamp(1610274923, 1),
113 | "t" : NumberLong(2)
114 | },
115 | "optimeDurable" : {
116 | "ts" : Timestamp(1610274923, 1),
117 | "t" : NumberLong(2)
118 | },
119 | "optimeDate" : ISODate("2021-01-10T10:35:23Z"),
120 | "optimeDurableDate" : ISODate("2021-01-10T10:35:23Z"),
121 | "lastHeartb
122 | eat" : ISODate("2021-01-10T10:35:25.957Z"),
123 | "lastHeartbeatRecv" : ISODate("2021-01-10T10:35:25.966Z"),
124 | "pingMs" : NumberLong(1),
125 | "lastHeartbeatMessage" : "",
126 | "syncSourceHost" : "dockerhost:56023",
127 | "syncSourceId" : 1,
128 | "infoMessage" : "",
129 | "configVersion" : 3,
130 | "configTerm" : 2
131 | },
132 | {
133 | "_id" : 3,
134 | "name" : "dockerhost:56026",
135 | "health" : 1,
136 | "state" : 2,
137 | "stateStr" : "SECONDARY",
138 | "uptime" : 2,
139 | "optime" : {
140 | "ts" : Timestamp(1610274923, 1),
141 | "t" : NumberLong(2)
142 | },
143 | "optimeDurable" : {
144 | "ts" : Timestamp(1610274923, 1),
145 | "t" : NumberLong(2)
146 | },
147 | "optimeDate" : ISODate("2021-01-10T10:35:23Z"),
148 | "optimeDurableDate" : ISODate("2021-01-10T10:35:23Z"),
149 | "lastHeartbeat" : ISODate("2021-01-10T10:35:25.957Z"),
150 | "lastHeartbeatRecv" : ISODate("2021-01-10T10:35:26.127Z"),
151 | "pingMs" : NumberLong(1),
152 | "lastHeartbeatMessage" : "",
153 | "syncSourceHost" : "",
154 | "syncSourceId" : -1,
155 | "infoMessage" : "",
156 | "configVersion" : 3,
157 | "configTerm" : 2
158 | }
159 | ],
160 | "o
161 | k" : 1,
162 | "$clusterTime" : {
163 | "clusterTime" : Timestamp(1610274923, 1),
164 | "signature" : {
165 | "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
166 | "keyId" : NumberLong(0)
167 | }
168 | },
169 | "operationTime" : Timestamp(1610274923, 1)
170 | }
171 |
--------------------------------------------------------------------------------
/src/test/resources/shell-output/rs-status-plain-wo-status.txt:
--------------------------------------------------------------------------------
1 | MongoDB shell version v3.4.22
2 |
3 | connecting to: mongodb://127.0.0.1:27017
4 |
5 | MongoDB server version: 3.4.22
6 |
--------------------------------------------------------------------------------
/src/test/resources/shell-output/rs-status-plain.txt:
--------------------------------------------------------------------------------
1 | MongoDB shell version v3.4.22
2 |
3 | connecting to: mongodb://127.0.0.1:27017
4 |
5 | MongoDB server version: 3.4.22
6 |
7 | { "ok" : 1 }
8 |
--------------------------------------------------------------------------------
/src/test/resources/shell-output/rs-status-wo-status.txt:
--------------------------------------------------------------------------------
1 | MongoDB shell version v4.2.0
2 |
3 | connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
4 |
5 | Implicit session: session { "id" : UUID("2def7590-ba68-4db3-9415-508467e48d81") }
6 |
7 | MongoDB server version: 4.2.0
8 |
9 | {
10 | "set" : "docker-rs",
11 | "date" : ISODate("2019-08-20T10:55:31.809Z"),
12 | "myState" : 1,
13 | "term" : NumberLong(1),
14 | "syncingTo" : "",
15 | "syncSourceHost" : "",
16 | "syncSourceId" : -1,
17 | "heartbeatIntervalMillis" : NumberLong(2000),
18 | "optimes" : {
19 | "lastCommittedOpTime" : {
20 | "ts" : Timestamp(1566298529, 1),
21 | "t" : NumberLong(1)
22 | },
23 | "lastCommittedWallTime" : ISODate("2019-08-20T10:55:29.393Z"),
24 | "readConcernMajorityOpTime" : {
25 | "ts" : Timestamp(1566298529, 1),
26 | "t" : NumberLong(1)
27 | },
28 | "readConcernMajorityWallTime" : ISODate("2019-08-20T10:55:29.393Z"),
29 | "appliedOpTime" : {
30 | "ts" : Timestamp(1566298529, 1),
31 | "t" : NumberLong(1)
32 | },
33 | "durableOpTime" : {
34 | "ts" : Timestamp(1566298529, 1),
35 | "t" : NumberLong(1)
36 | },
37 | "lastAppliedWallTime" : ISODate("2019-08-20T10:55:29.393Z"),
38 | "lastDurableWallTime" : ISODate("2019-08-20T10:55:29.393Z")
39 | },
40 | "lastStableRecoveryTimestamp" : Timestamp(1566298525, 2),
41 | "lastStableCheckpointTimestamp" : Timestamp(1566298525, 2),
42 | "members" : [
43 | {
44 | "_id" : 0,
45 | "name" : "dockerhost:33570",
46 | "ip" : "192.168.208.2",
47 | "health" : 1,
48 | "state" : 2,
49 | "stateStr" : "SECONDARY",
50 | "uptime" : 17,
51 | "optime" : {
52 | "ts" : Timestamp(1566298529, 1),
53 | "t" : NumberLong(1)
54 | },
55 | "optimeDurable" : {
56 | "ts" : Timestamp(1566298529, 1),
57 | "t" : NumberLong(1)
58 | },
59 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
60 | "optimeDurableDate" : ISODate("2019-08-20T10:55:29Z"),
61 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.405Z"),
62 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.538Z"),
63 | "pingMs" : NumberLong(2),
64 | "lastHeartbeatMessage" : "",
65 | "syncingTo" : "",
66 | "syncSourceHost" : "",
67 | "syncSourceId" : -1,
68 | "infoMessage" : "",
69 | "configVersion" : 2
70 | },
71 | {
72 | "_id" : 1,
73 | "name" : "dockerhost:33571",
74 | "ip" : "192.168.208.2",
75 | "health" : 1,
76 | "state" : 2,
77 | "stateStr" : "SECONDARY",
78 | "uptime" : 17,
79 | "optime" : {
80 | "ts" : Timestamp(1566298529, 1),
81 | "t" : NumberLong(1)
82 | },
83 | "optimeDurable" : {
84 | "ts" : Timestamp(1566298529, 1),
85 | "t" : NumberLong(1)
86 | },
87 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
88 | "optimeDurableDate" : ISODate("2019-08-20T10:55:29Z"),
89 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.405Z"),
90 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.528Z"),
91 | "pingMs" : NumberLong(5),
92 | "lastHeartbeatMessage" : "",
93 | "syncingTo" : "",
94 | "syncSourceHost" : "",
95 | "syncSourceId" : -1,
96 | "infoMessage" : "",
97 | "configVersion" : 2
98 | },
99 | {
100 | "_id" : 2,
101 | "name" : "dockerhost:33572",
102 | "ip" : "192.168.208.2",
103 | "health" : 1,
104 | "state" : 2,
105 | "stateStr" : "SECONDARY",
106 | "uptime" : 17,
107 | "optime" : {
108 | "ts" : Timestamp(1566298529, 1),
109 | "t" : NumberLong(1)
110 | },
111 | "optimeDurable" : {
112 | "ts" : Timestamp(1566298529, 1),
113 | "t" : NumberLong(1)
114 | },
115 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
116 | "optimeDurableDate" : ISODate("2019-08-20T10:55:29Z"),
117 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.453Z"),
118 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.525Z"),
119 | "pingMs" : NumberLong(5),
120 | "lastHeartbeatMessage" : "",
121 | "syncingTo" : "",
122 | "syncSourceHost" : "",
123 | "syncSourceId" : -1,
124 | "infoMessage" : "",
125 | "configVersion" : 2
126 | },
127 | {
128 | "_id" : 3,
129 | "name" : "dockerhost:33573",
130 | "ip" : "192.168.208.2",
131 | "health" : 1,
132 | "state" : 1,
133 | "stateStr" : "PRIMARY",
134 | "uptime" : 19,
135 | "optime" : {
136 | "ts" : Timestamp(1566298529, 1),
137 | "t" : NumberLong(1)
138 | },
139 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
140 | "syncingTo" : "",
141 | "syncSourceHost" : "",
142 | "syncSourceId" : -1,
143 | "infoMessage" : "could not find member to sync from",
144 | "electionTime" : Timestamp(1566298524, 1),
145 | "electionDate" : ISODate("2019-08-20T10:55:24Z"),
146 | "configVersion" : 2,
147 | "self" : true,
148 | "lastHeartbeatMessage" : ""
149 | },
150 | {
151 | "_id" : 4,
152 | "name" : "dockerhost:33574",
153 | "ip" : "192.168.208.2",
154 | "health" : 1,
155 | "state" : 7,
156 | "stateStr" : "ARBITER",
157 | "uptime" : 2,
158 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.418Z"),
159 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.593Z"),
160 | "pingMs" : NumberLong(10),
161 | "lastHeartbeatMessage" : "",
162 | "syncingTo" : "",
163 | "syncSourceHost" : "",
164 | "syncSourceId" : -1,
165 | "infoMessage" : "",
166 | "configVersion" : 2
167 | }
168 | ],
169 | "$clusterTime" : {
170 | "clusterTime" : Timestamp(1566298529, 1),
171 | "signature" : {
172 | "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
173 | "keyId" : NumberLong(0)
174 | }
175 | },
176 | "operationTime" : Timestamp(1566298529, 1)
177 | }
178 |
--------------------------------------------------------------------------------
/src/test/resources/shell-output/rs-status-wo-version.txt:
--------------------------------------------------------------------------------
1 | MongoDB shell version v4.2.0
2 |
3 | connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
4 |
5 | Implicit session: session { "id" : UUID("2def7590-ba68-4db3-9415-508467e48d81") }
6 |
7 | {
8 | "set" : "docker-rs",
9 | "date" : ISODate("2019-08-20T10:55:31.809Z"),
10 | "myState" : 1,
11 | "term" : NumberLong(1),
12 | "syncingTo" : "",
13 | "syncSourceHost" : "",
14 | "syncSourceId" : -1,
15 | "heartbeatIntervalMillis" : NumberLong(2000),
16 | "optimes" : {
17 | "lastCommittedOpTime" : {
18 | "ts" : Timestamp(1566298529, 1),
19 | "t" : NumberLong(1)
20 | },
21 | "lastCommittedWallTime" : ISODate("2019-08-20T10:55:29.393Z"),
22 | "readConcernMajorityOpTime" : {
23 | "ts" : Timestamp(1566298529, 1),
24 | "t" : NumberLong(1)
25 | },
26 | "readConcernMajorityWallTime" : ISODate("2019-08-20T10:55:29.393Z"),
27 | "appliedOpTime" : {
28 | "ts" : Timestamp(1566298529, 1),
29 | "t" : NumberLong(1)
30 | },
31 | "durableOpTime" : {
32 | "ts" : Timestamp(1566298529, 1),
33 | "t" : NumberLong(1)
34 | },
35 | "lastAppliedWallTime" : ISODate("2019-08-20T10:55:29.393Z"),
36 | "lastDurableWallTime" : ISODate("2019-08-20T10:55:29.393Z")
37 | },
38 | "lastStableRecoveryTimestamp" : Timestamp(1566298525, 2),
39 | "lastStableCheckpointTimestamp" : Timestamp(1566298525, 2),
40 | "members" : [
41 | {
42 | "_id" : 0,
43 | "name" : "dockerhost:33570",
44 | "ip" : "192.168.208.2",
45 | "health" : 1,
46 | "state" : 2,
47 | "stateStr" : "SECONDARY",
48 | "uptime" : 17,
49 | "optime" : {
50 | "ts" : Timestamp(1566298529, 1),
51 | "t" : NumberLong(1)
52 | },
53 | "optimeDurable" : {
54 | "ts" : Timestamp(1566298529, 1),
55 | "t" : NumberLong(1)
56 | },
57 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
58 | "optimeDurableDate" : ISODate("2019-08-20T10:55:29Z"),
59 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.405Z"),
60 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.538Z"),
61 | "pingMs" : NumberLong(2),
62 | "lastHeartbeatMessage" : "",
63 | "syncingTo" : "",
64 | "syncSourceHost" : "",
65 | "syncSourceId" : -1,
66 | "infoMessage" : "",
67 | "configVersion" : 2
68 | },
69 | {
70 | "_id" : 1,
71 | "name" : "dockerhost:33571",
72 | "ip" : "192.168.208.2",
73 | "health" : 1,
74 | "state" : 2,
75 | "stateStr" : "SECONDARY",
76 | "uptime" : 17,
77 | "optime" : {
78 | "ts" : Timestamp(1566298529, 1),
79 | "t" : NumberLong(1)
80 | },
81 | "optimeDurable" : {
82 | "ts" : Timestamp(1566298529, 1),
83 | "t" : NumberLong(1)
84 | },
85 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
86 | "optimeDurableDate" : ISODate("2019-08-20T10:55:29Z"),
87 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.405Z"),
88 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.528Z"),
89 | "pingMs" : NumberLong(5),
90 | "lastHeartbeatMessage" : "",
91 | "syncingTo" : "",
92 | "syncSourceHost" : "",
93 | "syncSourceId" : -1,
94 | "infoMessage" : "",
95 | "configVersion" : 2
96 | },
97 | {
98 | "_id" : 2,
99 | "name" : "dockerhost:33572",
100 | "ip" : "192.168.208.2",
101 | "health" : 1,
102 | "state" : 2,
103 | "stateStr" : "SECONDARY",
104 | "uptime" : 17,
105 | "optime" : {
106 | "ts" : Timestamp(1566298529, 1),
107 | "t" : NumberLong(1)
108 | },
109 | "optimeDurable" : {
110 | "ts" : Timestamp(1566298529, 1),
111 | "t" : NumberLong(1)
112 | },
113 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
114 | "optimeDurableDate" : ISODate("2019-08-20T10:55:29Z"),
115 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.453Z"),
116 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.525Z"),
117 | "pingMs" : NumberLong(5),
118 | "lastHeartbeatMessage" : "",
119 | "syncingTo" : "",
120 | "syncSourceHost" : "",
121 | "syncSourceId" : -1,
122 | "infoMessage" : "",
123 | "configVersion" : 2
124 | },
125 | {
126 | "_id" : 3,
127 | "name" : "dockerhost:33573",
128 | "ip" : "192.168.208.2",
129 | "health" : 1,
130 | "state" : 1,
131 | "stateStr" : "PRIMARY",
132 | "uptime" : 19,
133 | "optime" : {
134 | "ts" : Timestamp(1566298529, 1),
135 | "t" : NumberLong(1)
136 | },
137 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
138 | "syncingTo" : "",
139 | "syncSourceHost" : "",
140 | "syncSourceId" : -1,
141 | "infoMessage" : "could not find member to sync from",
142 | "electionTime" : Timestamp(1566298524, 1),
143 | "electionDate" : ISODate("2019-08-20T10:55:24Z"),
144 | "configVersion" : 2,
145 | "self" : true,
146 | "lastHeartbeatMessage" : ""
147 | },
148 | {
149 | "_id" : 4,
150 | "name" : "dockerhost:33574",
151 | "ip" : "192.168.208.2",
152 | "health" : 1,
153 | "state" : 7,
154 | "stateStr" : "ARBITER",
155 | "uptime" : 2,
156 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.418Z"),
157 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.593Z"),
158 | "pingMs" : NumberLong(10),
159 | "lastHeartbeatMessage" : "",
160 | "syncingTo" : "",
161 | "syncSourceHost" : "",
162 | "syncSourceId" : -1,
163 | "infoMessage" : "",
164 | "configVersion" : 2
165 | }
166 | ],
167 | "ok" : 1,
168 | "$clusterTime" : {
169 | "clusterTime" : Timestamp(1566298529, 1),
170 | "signature" : {
171 | "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
172 | "keyId" : NumberLong(0)
173 | }
174 | },
175 | "operationTime" : Timestamp(1566298529, 1)
176 | }
177 |
--------------------------------------------------------------------------------
/src/test/resources/shell-output/rs-status.txt:
--------------------------------------------------------------------------------
1 | MongoDB shell version v4.2.0
2 |
3 | connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
4 |
5 | Implicit session: session { "id" : UUID("2def7590-ba68-4db3-9415-508467e48d81") }
6 |
7 | MongoDB server version: 4.2.0
8 |
9 | {
10 | "set" : "docker-rs",
11 | "date" : ISODate("2019-08-20T10:55:31.809Z"),
12 | "myState" : 1,
13 | "term" : NumberLong(1),
14 | "syncingTo" : "",
15 | "syncSourceHost" : "",
16 | "syncSourceId" : -1,
17 | "heartbeatIntervalMillis" : NumberLong(2000),
18 | "optimes" : {
19 | "lastCommittedOpTime" : {
20 | "ts" : Timestamp(1566298529, 1),
21 | "t" : NumberLong(1)
22 | },
23 | "lastCommittedWallTime" : ISODate("2019-08-20T10:55:29.393Z"),
24 | "readConcernMajorityOpTime" : {
25 | "ts" : Timestamp(1566298529, 1),
26 | "t" : NumberLong(1)
27 | },
28 | "readConcernMajorityWallTime" : ISODate("2019-08-20T10:55:29.393Z"),
29 | "appliedOpTime" : {
30 | "ts" : Timestamp(1566298529, 1),
31 | "t" : NumberLong(1)
32 | },
33 | "durableOpTime" : {
34 | "ts" : Timestamp(1566298529, 1),
35 | "t" : NumberLong(1)
36 | },
37 | "lastAppliedWallTime" : ISODate("2019-08-20T10:55:29.393Z"),
38 | "lastDurableWallTime" : ISODate("2019-08-20T10:55:29.393Z")
39 | },
40 | "lastStableRecoveryTimestamp" : Timestamp(1566298525, 2),
41 | "lastStableCheckpointTimestamp" : Timestamp(1566298525, 2),
42 | "members" : [
43 | {
44 | "_id" : 0,
45 | "name" : "dockerhost:33570",
46 | "ip" : "192.168.208.2",
47 | "health" : 1,
48 | "state" : 2,
49 | "stateStr" : "SECONDARY",
50 | "uptime" : 17,
51 | "optime" : {
52 | "ts" : Timestamp(1566298529, 1),
53 | "t" : NumberLong(1)
54 | },
55 | "optimeDurable" : {
56 | "ts" : Timestamp(1566298529, 1),
57 | "t" : NumberLong(1)
58 | },
59 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
60 | "optimeDurableDate" : ISODate("2019-08-20T10:55:29Z"),
61 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.405Z"),
62 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.538Z"),
63 | "pingMs" : NumberLong(2),
64 | "lastHeartbeatMessage" : "",
65 | "syncingTo" : "",
66 | "syncSourceHost" : "",
67 | "syncSourceId" : -1,
68 | "infoMessage" : "",
69 | "configVersion" : 2
70 | },
71 | {
72 | "_id" : 1,
73 | "name" : "dockerhost:33571",
74 | "ip" : "192.168.208.2",
75 | "health" : 1,
76 | "state" : 2,
77 | "stateStr" : "SECONDARY",
78 | "uptime" : 17,
79 | "optime" : {
80 | "ts" : Timestamp(1566298529, 1),
81 | "t" : NumberLong(1)
82 | },
83 | "optimeDurable" : {
84 | "ts" : Timestamp(1566298529, 1),
85 | "t" : NumberLong(1)
86 | },
87 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
88 | "optimeDurableDate" : ISODate("2019-08-20T10:55:29Z"),
89 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.405Z"),
90 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.528Z"),
91 | "pingMs" : NumberLong(5),
92 | "lastHeartbeatMessage" : "",
93 | "syncingTo" : "",
94 | "syncSourceHost" : "",
95 | "syncSourceId" : -1,
96 | "infoMessage" : "",
97 | "configVersion" : 2
98 | },
99 | {
100 | "_id" : 2,
101 | "name" : "dockerhost:33572",
102 | "ip" : "192.168.208.2",
103 | "health" : 1,
104 | "state" : 2,
105 | "stateStr" : "SECONDARY",
106 | "uptime" : 17,
107 | "optime" : {
108 | "ts" : Timestamp(1566298529, 1),
109 | "t" : NumberLong(1)
110 | },
111 | "optimeDurable" : {
112 | "ts" : Timestamp(1566298529, 1),
113 | "t" : NumberLong(1)
114 | },
115 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
116 | "optimeDurableDate" : ISODate("2019-08-20T10:55:29Z"),
117 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.453Z"),
118 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.525Z"),
119 | "pingMs" : NumberLong(5),
120 | "lastHeartbeatMessage" : "",
121 | "syncingTo" : "",
122 | "syncSourceHost" : "",
123 | "syncSourceId" : -1,
124 | "infoMessage" : "",
125 | "configVersion" : 2
126 | },
127 | {
128 | "_id" : 3,
129 | "name" : "dockerhost:33573",
130 | "ip" : "192.168.208.2",
131 | "health" : 1,
132 | "state" : 1,
133 | "stateStr" : "PRIMARY",
134 | "uptime" : 19,
135 | "optime" : {
136 | "ts" : Timestamp(1566298529, 1),
137 | "t" : NumberLong(1)
138 | },
139 | "optimeDate" : ISODate("2019-08-20T10:55:29Z"),
140 | "syncingTo" : "",
141 | "syncSourceHost" : "",
142 | "syncSourceId" : -1,
143 | "infoMessage" : "could not find member to sync from",
144 | "electionTime" : Timestamp(1566298524, 1),
145 | "electionDate" : ISODate("2019-08-20T10:55:24Z"),
146 | "configVersion" : 2,
147 | "self" : true,
148 | "lastHeartbeatMessage" : ""
149 | },
150 | {
151 | "_id" : 4,
152 | "name" : "dockerhost:33574",
153 | "ip" : "192.168.208.2",
154 | "health" : 1,
155 | "state" : 7,
156 | "stateStr" : "ARBITER",
157 | "uptime" : 2,
158 | "lastHeartbeat" : ISODate("2019-08-20T10:55:31.418Z"),
159 | "lastHeartbeatRecv" : ISODate("2019-08-20T10:55:31.593Z"),
160 | "pingMs" : NumberLong(10),
161 | "lastHeartbeatMessage" : "",
162 | "syncingTo" : "",
163 | "syncSourceHost" : "",
164 | "syncSourceId" : -1,
165 | "infoMessage" : "",
166 | "configVersion" : 2
167 | }
168 | ],
169 | "ok" : 1,
170 | "$clusterTime" : {
171 | "clusterTime" : Timestamp(1566298529, 1),
172 | "signature" : {
173 | "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
174 | "keyId" : NumberLong(0)
175 | }
176 | },
177 | "operationTime" : Timestamp(1566298529, 1)
178 | }
--------------------------------------------------------------------------------
/src/test/resources/shell-output/timeout-exceeds.txt:
--------------------------------------------------------------------------------
1 | MongoDB shell version v4.4.0
2 |
3 | connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
4 |
5 | Implicit session: session { "id" : UUID("dd574fc5-528c-437b-8960-62859bc5c247") }
6 |
7 | MongoDB server version: 4.4.0
8 |
9 | {
10 | "operationTime" : Timestamp(1597732974, 1),
11 | "ok" : 0,
12 | "errmsg" : "operation exceeded time limit",
13 | "code" : 50,
14 | "codeName" : "MaxTimeMSExpired",
15 | "$clusterTime" : {
16 | "clusterTime" : Timestamp(1597732974, 1),
17 | "signature" : {
18 | "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
19 | "keyId" : NumberLong(0)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/testcov.exclude:
--------------------------------------------------------------------------------
1 | com/github/silaev/mongodb/replicaset/model/**
2 |
--------------------------------------------------------------------------------