Код: Выделить всё
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.1'
id 'io.spring.dependency-management' version '1.1.4'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
....
Код: Выделить всё
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(MongoPropertiesCustom.class)
@EnableReactiveMongoRepositories(basePackageClasses = {SubscriptionRepository.class})
public class MongoConfig extends AbstractReactiveMongoConfiguration {
private final MongoPropertiesCustom mongoPropertiesCustom;
@Override
protected String getDatabaseName() {
return mongoPropertiesCustom.getDatabase();
}
@Override
public MongoClient reactiveMongoClient() {
return MongoClients.create(mongoPropertiesCustom.buildUri());
}
@Bean
public ReactiveMongoTemplate reactiveMongoTemplate() {
return new ReactiveMongoTemplate(reactiveMongoClient(), getDatabaseName());
}
@Bean
public CommandLineRunner initializeIndexes(ReactiveMongoTemplate mongoTemplate) {
return args -> {
mongoTemplate.indexOps(SubscriptionDocument.class)
.ensureIndex(new Index().on("id", Sort.Direction.ASC).unique())
.subscribe();
mongoTemplate.indexOps(ProcessingError.class)
.ensureIndex(new Index().on("timestamp", Sort.Direction.DESC))
.subscribe();
};
}
@Bean
public ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory factory) {
return new ReactiveMongoTransactionManager(factory);
}
}
Код: Выделить всё
@ConfigurationProperties(prefix = "mongodb")
@Data
public class MongoPropertiesCustom {
private String host = "";
private int port = 0;
private String database = "";
private String username;
private String password;
private String authSource = "";
private String replicaSet;
public String buildUri() {
return String.format("mongodb://%s:%s@%s:%d/%s?authSource=%s&replicaSet=%s",
username, password, host, port, database, authSource, replicaSet);
}
}
Код: Выделить всё
@Component
@RequiredArgsConstructor
@Slf4j
public class SubscriptionRepository {
private final ReactiveMongoTemplate mongoTemplate;
private final ReactiveMongoTransactionManager transactionManager;
public Mono save(SubscriptionDocument subscription) {
TransactionalOperator transactionalOperator = TransactionalOperator.create(transactionManager);
return mongoTemplate.save(subscription)
.doOnSuccess(saved -> log.debug("Saved subscription: {}", saved))
.doOnError(error -> log.error("Error saving subscription: {}", error.getMessage()))
.as(transactionalOperator::transactional);
}
............
Код: Выделить всё
@SpringBootTest(classes = {
MongoConfig.class,
SubscriptionRepository.class
})
@ActiveProfiles("test")
@Testcontainers
class SubscriptionRepositoryTest {
private static final Logger log = LoggerFactory.getLogger(SubscriptionRepositoryTest.class);
@Container
static MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:6.0")
.withCommand(
"--replSet", "rs0",
"--bind_ip_all",
"--setParameter", "transactionLifetimeLimitSeconds=30"
);
@Autowired
private SubscriptionRepository subscriptionRepository;
@DynamicPropertySource
static void setProperties(DynamicPropertyRegistry registry) {
mongoDBContainer.start();
String containerIpAddress = mongoDBContainer.getHost();
Integer mappedPort = mongoDBContainer.getFirstMappedPort();
log.info("MongoDB container started at {}:{}", containerIpAddress, mappedPort);
initializeReplicaSet(mongoDBContainer);
registry.add("mongodb.host", () -> containerIpAddress);
registry.add("mongodb.port", () -> mappedPort);
registry.add("mongodb.database", () -> "test");
registry.add("mongodb.username", () -> "root");
registry.add("mongodb.password", () -> "root");
registry.add("mongodb.authSource", () -> "admin");
registry.add("mongodb.replicaSet", () -> "rs0");
}
private static void initializeReplicaSet(MongoDBContainer container) {
try {
log.info("Starting replica set initialization...");
Thread.sleep(1000);
String containerIpAddress = container.getHost();
Integer mappedPort = container.getFirstMappedPort();
log.info("Using host: {}, port: {}", containerIpAddress, mappedPort);
String rsConfig = String.format(
"rs.initiate({" +
"_id: 'rs0'," +
"members: [{" +
"_id: 0," +
"host: '%s:%d'," +
"priority: 1" +
"}]," +
"settings: {" +
"electionTimeoutMillis: 2000" +
"}" +
"})",
containerIpAddress, mappedPort
);
log.info("Initializing replica set with config: {}", rsConfig);
org.testcontainers.containers.Container.ExecResult initResult = container.execInContainer(
"mongosh",
"--eval",
rsConfig
);
log.info("Initialization result - stdout: {}", initResult.getStdout());
org.testcontainers.containers.Container.ExecResult statusResult = container.execInContainer(
"mongosh",
"--eval",
"rs.status()"
);
log.info("Replica set status: {}", statusResult.getStdout());
Thread.sleep(2000);
ConnectionString connectionString = new ConnectionString(
String.format("mongodb://%s:%d/?directConnection=true",
containerIpAddress, mappedPort)
);
try (MongoClient testClient = MongoClients.create(connectionString)) {
Mono.from(testClient.getDatabase("admin")
.runCommand(new Document("ping", 1)))
.block(Duration.ofSeconds(10));
log.info("Replica set initialized successfully");
String createUserCommand = "db.getSiblingDB('admin').createUser({" +
"user: 'root'," +
"pwd: 'root'," +
"roles: ['root']" +
"})";
org.testcontainers.containers.Container.ExecResult createUserResult = container.execInContainer(
"mongosh",
"--eval",
createUserCommand
);
log.info("Create user result: {}", createUserResult.getStdout());
String checkUserCommand = "db.getSiblingDB('admin').getUser('root')";
org.testcontainers.containers.Container.ExecResult checkUserResult = container.execInContainer(
"mongosh",
"--eval",
checkUserCommand
);
log.info("User check result: {}", checkUserResult.getStdout());
String authTestCommand = "db.getSiblingDB('admin').auth('root', 'root')";
org.testcontainers.containers.Container.ExecResult authTestResult = container.execInContainer(
"mongosh",
"--eval",
authTestCommand
);
log.info("Auth test result: {}", authTestResult.getStdout());
}
} catch (Exception e) {
log.error("Failed to initialize replica set", e);
throw new RuntimeException("Failed to initialize replica set", e);
}
}
@BeforeEach
void setUp() {
log.info("Cleaning up database before test");
subscriptionRepository.dropAllCollections()
.doOnSuccess(v -> log.info("Database cleaned successfully"))
.doOnError(e -> log.error("Error cleaning database", e))
.block();
}
.....
2025-01-18T20:43:08.861+03: 00 INFO 2560 --- [потребитель] [-localhost:3316] org.mongodb.driver.cluster: Сервер localhost:3316 больше не является членом набор реплик. Удаление кластера из клиентского представления.
2025-01-18T20:43:08.862+03:00 INFO 2560 --- [consumer] [-localhost:3316] org.mongodb.driver.cluster: обнаружен основной набор реплик. localhost: 3316 с максимальным идентификатором выборов 7ffffff0000000000000001 и максимальный набор версии 1
2025-01-18T20:43:09.233+03:00 INFO 2560 --- [потребитель] [Тестовый работник] c.repository.SubscriptionRepositoryTest : Запущен SubscriptionRepositoryTest за 8,815 секунд (процесс работает для 14.105)
2025-01-18T20:43:09.317+03:00 INFO 2560 --- [потребитель] [тестовый работник] org.mongodb.driver.cluster: сервер не выбран WritableServerSelector из описания кластера ClusterDescription {type=REPLICA_SET, ConnectionMode=MULTIPLE, serverDescriptions = [ServerDescription {адрес = localhost: 3316, тип = НЕИЗВЕСТНО, состояние = ПОДКЛЮЧЕНИЕ}]}. Ожидание 30000 мс до истечения времени
2025-01-18T20:43:09.324+03:00 INFO 2560 --- [потребитель] [Тестовый работник] org.mongodb.driver.cluster: сервер не выбран WritableServerSelector из описание кластера ClusterDescription{type=REPLICA_SET, ConnectionMode = MULTIPLE, serverDescriptions = [ServerDescription {адрес = localhost: 3316, тип = НЕИЗВЕСТНО, состояние = ПОДКЛЮЧЕНИЕ}]}. Ожидание 30000 мс до истечения времени ожидания
2025-01-18T20:43:10.541+03:00 INFO 2560 --- [потребитель] [ Тестовый работник] c.repository.SubscriptionRepositoryTest : Очистка базы данных перед тестированием
2025-01-18T20:43:10.638+03:00 INFO 2560 --- [потребитель] [Тестовый работник] org.mongodb.driver.cluster: сервер не выбран ReadPreferenceServerSelector{readPreference=primary} из описания кластера ClusterDescription{ тип = REPLICA_SET, ConnectionMode = MULTIPLE, serverDescriptions = [ServerDescription {адрес = localhost: 3316, тип = НЕИЗВЕСТНО, состояние = ПОДКЛЮЧЕНИЕ}]}. Ожидание 30000 мс до истечения времени
2025-01-18T20:43:11.151+03:00 INFO 2560 --- [потребитель] [3de90b922:27017] org.mongodb.driver.cluster: Исключение в потоке монитора во время подключение к серверу 3c43de90b922:27017
com.mongodb.MongoSocketException: этот хост неизвестен (3c43de90b922)
в com.mongodb.ServerAddress.getSocketAddresses(ServerAddress. Ява:221) ~[mongodb-driver-core-4.11.1.jar:na]
Есть ли у кого-нибудь идеи, почему инициализация набора реплик проходит успешно, как и проверки, а затем ConnectionString для ReactiveMongoTemplate тоже успешно формируется, но в какой-то момент времени внешний адрес не обращается, то есть MongoDB внутри контейнера использует свое внутреннее имя хоста (22e0b261103c), но это имя хоста недоступно вне контейнера?
Подробнее здесь: https://stackoverflow.com/questions/793 ... is-unknown