You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sd...@apache.org on 2022/03/18 14:39:23 UTC

[ignite-3] branch ignite-15114 updated (4d5a520 -> be49fde)

This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a change to branch ignite-15114
in repository https://gitbox.apache.org/repos/asf/ignite-3.git.


    omit 4d5a520  IGNITE-16528 Implement init command handling
     add 6e169f3  IGNITE-16624 Tx state doesn't replicate within raft group if tx coordinator is collocated with corresponding raft leader.
     add 8b9562e  Merge branch 'main' of https://github.com/apache/ignite-3 into main
     add 4a05cf2  IGNITE-16393 Move network's user object serialization to the user thread (#705)
     add 66f5136  IGNITE-16593 Schema should keep colocation information (#708)
     add b5b3e71  IGNITE-16654 Added an ability to compare entry values in meta storage conditional updates. Fixes #706
     add 71ec36c  IGNITE-16630 Fix JDBC tests related to MERGE statement - Fixes #710.
     add 029d3a4  IGNITE-16219 ItJdbcJoinsSelfTest unmute test - Fixes #716.
     add 7566234  IGNITE-16678 RaftGroupService#transferLeadership fixed.
     add f8f4b2a  IGNITE-16222 .NET: Thin 3.0: Add RetryPolicy (#719)
     add d8959c0  IGNITE-16613 [Compute Grid] IgniteCompute facade and ComputeJob interface (#715)
     add 8d08ada  IGNITE-16591 DDL syntax COLOCATE BY (#711)
     add 3d7ed2f  IGNITE-16585 Fixed NPE during exception handling in DelegatingStateMachine (#725)
     add 79a4b40  IGNITE-16656 Adoption of a bunch of calcite related tickets from Ignite-2 - Fixes #707.
     add 066ba91  IGNITE-16699 Properly stop executors in the network module (#731)
     add 5687f7f  IGNITE-16468 Extend test coverage for RecordBinaryView - Fixes #729.
     add 722fa5f  Disable tests under correct link
     add c7b25f0  IGNITE-16594 Colocation key hash code calculation (#721)
     add 11f43ba  IGNITE-16709 Implement JVM pause detector (#735)
     add 1447c85  IGNITE-16704 Removed excessive methods from BinaryRow interface. (#732)
     add c526fc3  IGNITE-16688 Extract RocksDB snapshots into a separate class (#723)
     add 6064de5  IGNITE-16674 Fix incremental compilation for network messages (#714)
     add b703159  IGNITE-16616 Implement execute method of IgniteCompute interface
     new be49fde  IGNITE-16528 Implement init command handling

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (4d5a520)
            \
             N -- N -- N   refs/heads/ignite-15114 (be49fde)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 modules/api/pom.xml                                |   5 +
 .../src/main/java/org/apache/ignite/Ignite.java    |  11 +
 .../org/apache/ignite/compute/ComputeJob.java}     |  19 +-
 .../org/apache/ignite/compute/IgniteCompute.java   |  52 +++
 .../ignite/compute/JobExecutionContext.java}       |  12 +-
 .../ComputeConfigurationSchema.java}               |  24 +-
 .../table/PrimaryKeyConfigurationSchema.java       |   4 +-
 .../schema/definition/PrimaryKeyDefinition.java    |   7 +-
 .../ignite/schema/definition/TableDefinition.java  |   2 +-
 .../builder/PrimaryKeyDefinitionBuilder.java       |  12 +-
 .../ignite/internal/client/TcpIgniteClient.java    |   7 +
 .../org/apache/ignite/client/fakes/FakeIgnite.java |   7 +
 modules/{raft-client => compute}/pom.xml           |  32 +-
 .../ignite/internal/compute/ComputeComponent.java  |  70 ++++
 .../internal/compute/ComputeComponentImpl.java     | 254 ++++++++++++
 .../internal/compute/ComputeMessageTypes.java}     |  18 +-
 .../ignite/internal/compute/IgniteComputeImpl.java |  81 ++++
 .../internal/compute/JobExecutionContextImpl.java} |  23 +-
 .../internal/compute/message/ExecuteRequest.java}  |  30 +-
 .../internal/compute/message/ExecuteResponse.java} |  32 +-
 .../internal/compute/ComputeComponentImplTest.java | 451 +++++++++++++++++++++
 .../internal/compute/IgniteComputeImplTest.java    |  90 ++++
 .../compute/JobExecutionContextImplTest.java}      |  28 +-
 .../internal/components/LongJvmPauseDetector.java  | 249 ++++++++++++
 .../ignite/internal/util/HashCalculator.java       | 260 ++++++++++++
 .../org/apache/ignite/internal/util/HashUtils.java | 217 ++++++++++
 .../apache/ignite/internal/util/HashUtilsTest.java | 137 +++++++
 .../client/ItMetaStorageServiceTest.java           |  38 +-
 .../internal/metastorage/client/Conditions.java    |  18 +-
 .../ignite/internal/metastorage/client/If.java     |   4 +-
 .../metastorage/client/SimpleCondition.java        | 166 +++++---
 .../internal/metastorage/common/ConditionType.java |  36 +-
 .../ignite/internal/metastorage/server/If.java     |   2 +-
 .../metastorage/server/KeyValueStorage.java        |   3 +-
 .../metastorage/server/ValueCondition.java         |  42 +-
 .../server/persistence/RocksDbKeyValueStorage.java | 118 ++----
 .../server/raft/MetaStorageListener.java           |   8 +
 .../metastorage/server/RevisionConditionTest.java  |   4 +-
 .../metastorage/server/ValueConditionTest.java     |  74 ++++
 .../processor/IncrementalCompilationConfig.java    | 172 ++++++++
 .../internal/network/processor/MessageClass.java   |   9 +
 .../processor/TransferableObjectProcessor.java     |  72 +++-
 .../messages/MessageBuilderGenerator.java          |  91 +++--
 .../messages/MessageFactoryGenerator.java          |   4 +-
 .../processor/messages/MessageImplGenerator.java   | 309 +++++++++++++-
 .../MessageDeserializerGenerator.java              |  11 +-
 .../serialization/MessageReaderMethodResolver.java |   5 +-
 .../serialization/MessageSerializerGenerator.java  |   4 +-
 .../serialization/MessageWriterMethodResolver.java |   5 +-
 .../SerializationFactoryGenerator.java             |   2 +-
 modules/network-api/pom.xml                        |   7 +-
 .../org/apache/ignite/network/NetworkMessage.java  |  10 +
 .../network/serialization/MessageReader.java       |  10 -
 .../network/serialization/MessageWriter.java       |  11 -
 modules/network/pom.xml                            |  12 +
 .../network/netty/ItConnectionManagerTest.java     |  20 +-
 .../network/processor/InMemoryJavaFileManager.java | 147 +++++++
 ...TransferableObjectProcessorIncrementalTest.java | 318 +++++++++++++++
 .../ItTransferableObjectProcessorTest.java         |   4 +-
 .../network/recovery/ItRecoveryHandshakeTest.java  |   6 +-
 .../scalecube/ItScaleCubeNetworkMessagingTest.java |  10 +-
 .../network/processor/AllTypesMessage.java         |   0
 .../network/processor/ConflictingTypeMessage.java  |   0
 .../network/processor/ITTestMessageGroup.java      |   0
 .../network/processor/InheritedMessageClash.java   |   0
 .../processor/InvalidAnnotatedTypeMessage.java     |   0
 .../processor/InvalidParameterGetterMessage.java   |   0
 .../processor/InvalidReturnTypeGetterMessage.java  |   0
 .../internal/network/processor/SecondGroup.java    |   0
 .../network/processor/TransitiveMessage.java       |   0
 .../processor/UnmarshallableTypeMessage.java       |   0
 .../UnmarshallableTypeNonSerializableMessage.java  |   0
 .../internal/network/NetworkMessageTypes.java      |   6 +
 .../network/direct/DirectMessageReader.java        |  12 -
 .../network/direct/DirectMessageWriter.java        |  10 -
 .../direct/stream/DirectByteBufferStream.java      |  19 -
 .../stream/DirectByteBufferStreamImplV1.java       | 102 -----
 ...equest.java => ClassDescriptorListMessage.java} |  18 +-
 .../internal/network/netty/ConnectionManager.java  |  26 +-
 .../internal/network/netty/InNetworkObject.java    |  70 ++++
 .../internal/network/netty/InboundDecoder.java     |  13 +-
 .../internal/network/netty/MessageHandler.java     |  14 +-
 .../network/netty/NamedNioEventLoopGroup.java      |  18 +-
 .../ignite/internal/network/netty/NettyClient.java |  11 +-
 .../ignite/internal/network/netty/NettySender.java |  19 +-
 .../ignite/internal/network/netty/NettyServer.java |  10 +-
 .../internal/network/netty/OutboundEncoder.java    |  59 ++-
 .../recovery/RecoveryClientHandshakeManager.java   |   4 +-
 .../recovery/RecoveryServerHandshakeManager.java   |   4 +-
 .../PerSessionSerializationService.java            |  61 ++-
 .../serialization/SerializationService.java        |  44 --
 .../ignite/network/DefaultMessagingService.java    |  93 ++++-
 .../ignite/network/NettyBootstrapFactory.java      |  12 +
 ...StaticNodeFinder.java => OutNetworkObject.java} |  29 +-
 .../scalecube/ScaleCubeClusterServiceFactory.java  |  47 ++-
 .../ScaleCubeDirectMarshallerTransport.java        |  33 +-
 .../internal/network/netty/NettyClientTest.java    |   4 +-
 .../internal/network/netty/NettyServerTest.java    |   2 +-
 .../network/serialization/MarshallableTest.java    |  57 ++-
 .../Buffers/PooledArrayBufferWriterTests.cs        |  66 +--
 .../dotnet/Apache.Ignite.Tests/FakeServer.cs       | 201 +++++++++
 .../dotnet/Apache.Ignite.Tests/FakeServerTests.cs  |  75 ++++
 .../Apache.Ignite.Tests/ProjectFilesTests.cs       |   1 +
 .../Poco.cs => Proto/ClientOpExtensionsTest.cs}    |  25 +-
 .../Proto/MessagePackExtensionsTest.cs             |   8 +-
 .../dotnet/Apache.Ignite.Tests/RetryPolicyTests.cs | 218 ++++++++++
 .../{DisposeAction.cs => RetryReadPolicyTests.cs}  |  31 +-
 .../Serialization/ObjectSerializerHandlerTests.cs  |  12 +-
 .../dotnet/Apache.Ignite/ClientOperationType.cs    | 112 +++++
 .../{ClientErrorCode.cs => IRetryPolicy.cs}        |  24 +-
 .../{Log/LogLevel.cs => IRetryPolicyContext.cs}    |  30 +-
 .../Apache.Ignite/IgniteClientConfiguration.cs     |  12 +
 .../Internal/Buffers/PooledArrayBufferWriter.cs    |  54 +--
 .../Apache.Ignite/Internal/ClientFailoverSocket.cs | 105 ++++-
 .../dotnet/Apache.Ignite/Internal/ClientSocket.cs  | 166 ++++++--
 .../Apache.Ignite/Internal/Proto/ClientOp.cs       |   6 -
 .../Internal/Proto/ClientOpExtensions.cs           |  61 +++
 .../Internal/Proto/MessagePackUtil.cs              |  66 ++-
 ...niteClientInternal.cs => RetryPolicyContext.cs} |  43 +-
 .../Apache.Ignite/Internal/Table/RecordView.cs     |  14 +-
 .../dotnet/Apache.Ignite/Internal/Table/Table.cs   |  25 +-
 .../Proto/ProtoCommon.cs => RetryAllPolicy.cs}     |  10 +-
 .../{IgniteClient.cs => RetryLimitPolicy.cs}       |  27 +-
 .../{IIgniteClient.cs => RetryNonePolicy.cs}       |  18 +-
 .../dotnet/Apache.Ignite/RetryReadPolicy.cs        |  66 +++
 modules/raft/pom.xml                               |   1 -
 .../internal/raft/ItRaftGroupServiceTest.java      | 162 ++++++++
 .../raft/server/ItJraftCounterServerTest.java      |  42 +-
 .../internal/raft/server/impl/JraftServerImpl.java |   8 +-
 .../java/org/apache/ignite/raft/jraft/Status.java  |   8 +-
 .../raft/jraft/rpc/impl/RaftGroupServiceImpl.java  |   9 +-
 .../raft/jraft/core/RaftGroupServiceTest.java      |   3 +-
 .../ignite/internal/rocksdb/ColumnFamily.java      |  75 ++--
 .../apache/ignite/internal/rocksdb/RocksUtils.java |  39 --
 .../rocksdb/snapshot/ColumnFamilyRange.java        | 103 +++++
 .../rocksdb/snapshot/RocksSnapshotManager.java     | 213 ++++++++++
 modules/runner/pom.xml                             |   5 +
 .../internal/AbstractClusterIntegrationTest.java   | 141 +++++++
 .../ignite/internal/compute/ItComputeTest.java     | 170 ++++++++
 .../runner/app/ItDynamicTableCreationTest.java     |   6 +-
 .../runner/app/jdbc/ItJdbcBatchSelfTest.java       | 161 ++------
 .../runner/app/jdbc/ItJdbcJoinsSelfTest.java       |   1 -
 .../sql/engine/AbstractBasicIntegrationTest.java   |   4 +
 .../internal/sql/engine/ItAggregatesTest.java      |   4 +-
 .../internal/sql/engine/ItCreateTableDdlTest.java  |  96 ++++-
 .../internal/sql/engine/ItDataTypesTest.java       |  59 +++
 .../ignite/internal/sql/engine/ItDmlTest.java      |   1 +
 .../ignite/internal/sql/engine/ItIntervalTest.java |  50 +--
 .../internal/sql/engine/ItMixedQueriesTest.java    |   2 +-
 .../internal/sql/engine/ItSortAggregateTest.java   |   6 +-
 .../org/apache/ignite/internal/app/IgniteImpl.java |  49 ++-
 .../CoreLocalConfigurationModule.java              |   4 +-
 .../CoreLocalConfigurationModuleTest.java          |   6 +
 .../apache/ignite/internal/schema/BinaryRow.java   |  68 +---
 .../ignite/internal/schema/ByteBufferRow.java      |  88 +---
 .../ignite/internal/schema/SchemaDescriptor.java   |  20 +-
 .../SchemaConfigurationConverter.java              |   4 +-
 .../configuration/SchemaDescriptorConverter.java   |   2 +-
 .../schema/configuration/TableValidatorImpl.java   |   4 +-
 .../schema/definition/TableDefinitionImpl.java     |  10 +-
 .../builder/PrimaryKeyDefinitionBuilderImpl.java   |  30 +-
 .../builder/TableDefinitionBuilderImpl.java        |  17 +-
 .../definition/index/PrimaryKeyDefinitionImpl.java |  32 +-
 .../marshaller/schema/SchemaSerializerImpl.java    |  20 +-
 .../internal/schema/row/ExpandableByteBuf.java     |   2 +-
 .../org/apache/ignite/internal/schema/row/Row.java | 384 +++++++++---------
 .../ignite/internal/schema/row/VarTableFormat.java |  40 +-
 .../ignite/internal/util/ColocationUtils.java      | 142 +++++++
 .../internal/schema/SchemaConfigurationTest.java   |  44 +-
 .../ignite/internal/schema/SchemaTestUtils.java    |  20 +
 .../PrimaryKeyDefinitionDefinitionBuilderTest.java |  20 +-
 .../SchemaConfigurationConverterTest.java          |   2 +-
 .../SchemaDescriptorConverterTest.java             |   8 +-
 .../configuration/TableValidatorImplTest.java      |   2 +-
 .../schema/serializer/AbstractSerializerTest.java  |   2 +-
 modules/sql-engine/src/main/codegen/config.fmpp    |   2 +
 .../src/main/codegen/includes/parserImpls.ftl      |  17 +-
 .../internal/sql/engine/exec/ExecutionContext.java |   3 -
 .../sql/engine/exec/ddl/DdlCommandHandler.java     |   2 +-
 .../sql/engine/exec/exp/agg/Accumulators.java      | 215 ++--------
 .../sql/engine/prepare/ddl/CreateTableCommand.java |  25 +-
 .../prepare/ddl/DdlSqlToCommandConverter.java      |   9 +
 .../engine/rel/agg/IgniteSortAggregateBase.java    |  24 +-
 .../FilterSpoolMergeToSortedIndexSpoolRule.java    |  61 ++-
 .../engine/rule/SortAggregateConverterRule.java    |   7 +-
 .../sql/engine/sql/IgniteSqlCreateTable.java       |  33 +-
 .../internal/sql/engine/trait/TraitUtils.java      |  22 +-
 .../sql/engine/type/IgniteTypeFactory.java         |  14 +-
 .../internal/sql/engine/type/IgniteTypeSystem.java |  64 +++
 .../ignite/internal/sql/engine/util/RexUtils.java  |  19 +-
 .../ignite/internal/sql/engine/util/TypeUtils.java |  31 +-
 .../sql/engine/exec/rel/BaseAggregateTest.java     |  96 +++++
 .../sql/engine/planner/AbstractPlannerTest.java    |  32 ++
 .../planner/AggregateDistinctPlannerTest.java      |   4 +-
 .../sql/engine/planner/AggregatePlannerTest.java   |  86 +++-
 .../CorrelatedNestedLoopJoinPlannerTest.java       |   8 +-
 .../engine/planner/HashAggregatePlannerTest.java   |   4 +-
 .../engine/planner/HashIndexSpoolPlannerTest.java  |   8 +-
 .../sql/engine/planner/LimitOffsetPlannerTest.java |  20 +-
 .../engine/planner/SortAggregatePlannerTest.java   |   7 +-
 .../planner/SortedIndexSpoolPlannerTest.java       |   8 +-
 .../internal/sql/engine/sql/SqlDdlParserTest.java  |  69 ++++
 .../ignite/internal/storage/PartitionStorage.java  |   4 +-
 .../storage/AbstractPartitionStorageTest.java      |   6 +-
 .../basic/ConcurrentHashMapPartitionStorage.java   |   2 +-
 .../storage/rocksdb/RocksDbMetaStorage.java        |   7 +-
 .../storage/rocksdb/RocksDbPartitionStorage.java   |  97 ++---
 .../storage/rocksdb/RocksDbTableStorage.java       | 132 ++----
 .../rocksdb/index/RocksDbSortedIndexStorage.java   |   2 +-
 .../storage/rocksdb/RocksDbTableStorageTest.java   |  84 ++++
 ...butedTestThreeNodesThreeReplicasCollocated.java |  26 ++
 .../schema/marshaller/TupleMarshallerImpl.java     |   2 +
 .../table/ColocationHashCalculationTest.java       | 143 +++++++
 .../internal/table/InteropOperationsTest.java      |   4 +
 .../table/KeyValueBinaryViewOperationsTest.java    |   4 +
 .../KeyValueViewOperationsSimpleSchemaTest.java    |   4 +
 .../internal/table/KeyValueViewOperationsTest.java |   4 +
 .../internal/table/MessagingServiceTestUtils.java  |  67 +++
 .../table/RecordBinaryViewOperationsTest.java      | 264 ++++++++++++
 .../internal/table/RecordViewOperationsTest.java   |   4 +
 .../internal/table/SchemaValidationTest.java       |   4 +
 .../ignite/internal/table/TxAbstractTest.java      |   2 +-
 .../apache/ignite/internal/table/TxLocalTest.java  |   4 +
 modules/transactions/pom.xml                       |   5 +
 .../ignite/internal/tx/impl/TransactionImpl.java   |   3 +-
 parent/pom.xml                                     |  12 +
 pom.xml                                            |   1 +
 227 files changed, 8084 insertions(+), 2241 deletions(-)
 copy modules/{page-memory/src/main/java/org/apache/ignite/internal/pagememory/io/PageIoModule.java => api/src/main/java/org/apache/ignite/compute/ComputeJob.java} (68%)
 create mode 100644 modules/api/src/main/java/org/apache/ignite/compute/IgniteCompute.java
 copy modules/{transactions/src/main/java/org/apache/ignite/internal/tx/TxState.java => api/src/main/java/org/apache/ignite/compute/JobExecutionContext.java} (82%)
 copy modules/api/src/main/java/org/apache/ignite/configuration/schemas/{rest/RestConfigurationSchema.java => compute/ComputeConfigurationSchema.java} (69%)
 copy modules/{raft-client => compute}/pom.xml (88%)
 create mode 100644 modules/compute/src/main/java/org/apache/ignite/internal/compute/ComputeComponent.java
 create mode 100644 modules/compute/src/main/java/org/apache/ignite/internal/compute/ComputeComponentImpl.java
 copy modules/{transactions/src/main/java/org/apache/ignite/internal/tx/message/TxMessageGroup.java => compute/src/main/java/org/apache/ignite/internal/compute/ComputeMessageTypes.java} (64%)
 create mode 100644 modules/compute/src/main/java/org/apache/ignite/internal/compute/IgniteComputeImpl.java
 copy modules/{api/src/main/java/org/apache/ignite/schema/definition/index/SortedIndexDefinition.java => compute/src/main/java/org/apache/ignite/internal/compute/JobExecutionContextImpl.java} (67%)
 copy modules/{sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/ErrorMessage.java => compute/src/main/java/org/apache/ignite/internal/compute/message/ExecuteRequest.java} (65%)
 copy modules/{transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishRequest.java => compute/src/main/java/org/apache/ignite/internal/compute/message/ExecuteResponse.java} (55%)
 create mode 100644 modules/compute/src/test/java/org/apache/ignite/internal/compute/ComputeComponentImplTest.java
 create mode 100644 modules/compute/src/test/java/org/apache/ignite/internal/compute/IgniteComputeImplTest.java
 copy modules/{core/src/test/java/org/apache/ignite/internal/util/io/IgniteUnsafeDataInputTest.java => compute/src/test/java/org/apache/ignite/internal/compute/JobExecutionContextImplTest.java} (62%)
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/components/LongJvmPauseDetector.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/util/HashCalculator.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/util/HashUtilsTest.java
 create mode 100644 modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/IncrementalCompilationConfig.java
 create mode 100644 modules/network/src/integrationTest/java/org/apache/ignite/internal/network/processor/InMemoryJavaFileManager.java
 create mode 100644 modules/network/src/integrationTest/java/org/apache/ignite/internal/network/processor/ItTransferableObjectProcessorIncrementalTest.java
 rename modules/{network-annotation-processor => network}/src/integrationTest/java/org/apache/ignite/internal/network/processor/ItTransferableObjectProcessorTest.java (97%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/AllTypesMessage.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/ConflictingTypeMessage.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/ITTestMessageGroup.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/InheritedMessageClash.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/InvalidAnnotatedTypeMessage.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/InvalidParameterGetterMessage.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/InvalidReturnTypeGetterMessage.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/SecondGroup.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/TransitiveMessage.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/UnmarshallableTypeMessage.java (100%)
 rename modules/{network-annotation-processor => network}/src/integrationTest/resources/org/apache/ignite/internal/network/processor/UnmarshallableTypeNonSerializableMessage.java (100%)
 copy modules/network/src/main/java/org/apache/ignite/internal/network/message/{InvokeRequest.java => ClassDescriptorListMessage.java} (70%)
 create mode 100644 modules/network/src/main/java/org/apache/ignite/internal/network/netty/InNetworkObject.java
 copy modules/network/src/main/java/org/apache/ignite/network/{StaticNodeFinder.java => OutNetworkObject.java} (54%)
 create mode 100644 modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
 create mode 100644 modules/platforms/dotnet/Apache.Ignite.Tests/FakeServerTests.cs
 copy modules/platforms/dotnet/Apache.Ignite.Tests/{Table/Poco.cs => Proto/ClientOpExtensionsTest.cs} (61%)
 create mode 100644 modules/platforms/dotnet/Apache.Ignite.Tests/RetryPolicyTests.cs
 copy modules/platforms/dotnet/Apache.Ignite.Tests/{DisposeAction.cs => RetryReadPolicyTests.cs} (60%)
 create mode 100644 modules/platforms/dotnet/Apache.Ignite/ClientOperationType.cs
 copy modules/platforms/dotnet/Apache.Ignite/{ClientErrorCode.cs => IRetryPolicy.cs} (61%)
 copy modules/platforms/dotnet/Apache.Ignite/{Log/LogLevel.cs => IRetryPolicyContext.cs} (67%)
 create mode 100644 modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientOpExtensions.cs
 copy modules/platforms/dotnet/Apache.Ignite/Internal/{IgniteClientInternal.cs => RetryPolicyContext.cs} (52%)
 copy modules/platforms/dotnet/Apache.Ignite/{Internal/Proto/ProtoCommon.cs => RetryAllPolicy.cs} (79%)
 copy modules/platforms/dotnet/Apache.Ignite/{IgniteClient.cs => RetryLimitPolicy.cs} (60%)
 copy modules/platforms/dotnet/Apache.Ignite/{IIgniteClient.cs => RetryNonePolicy.cs} (73%)
 create mode 100644 modules/platforms/dotnet/Apache.Ignite/RetryReadPolicy.cs
 create mode 100644 modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItRaftGroupServiceTest.java
 create mode 100644 modules/rocksdb-common/src/main/java/org/apache/ignite/internal/rocksdb/snapshot/ColumnFamilyRange.java
 create mode 100644 modules/rocksdb-common/src/main/java/org/apache/ignite/internal/rocksdb/snapshot/RocksSnapshotManager.java
 create mode 100644 modules/runner/src/integrationTest/java/org/apache/ignite/internal/AbstractClusterIntegrationTest.java
 create mode 100644 modules/runner/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTest.java
 create mode 100644 modules/schema/src/main/java/org/apache/ignite/internal/util/ColocationUtils.java
 create mode 100644 modules/table/src/test/java/org/apache/ignite/internal/table/ColocationHashCalculationTest.java
 create mode 100644 modules/table/src/test/java/org/apache/ignite/internal/table/MessagingServiceTestUtils.java

[ignite-3] 01/01: IGNITE-16528 Implement init command handling

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-15114
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit be49fde414e6e40d18e3135a977e59a01c938a89
Author: Alexander Polovtcev <al...@gmail.com>
AuthorDate: Wed Mar 9 17:17:07 2022 +0300

    IGNITE-16528 Implement init command handling
---
 examples/config/ignite-config.json                 |   5 -
 examples/pom.xml                                   |  12 +
 .../ignite/example/AbstractExamplesTest.java       |  59 +++
 .../apache/ignite/example/ExampleTestUtils.java    |   2 +-
 .../example/sql/jdbc/ItSqlExamplesTest.java}       |  54 +--
 .../ignite/example/table/ItTableExamplesTest.java} |  54 +--
 .../example/tx/ItTransactionsExamplesTest.java     |  40 ++
 .../example/tx/TransactionsExamplesTest.java       |  88 -----
 .../src/main/java/org/apache/ignite/Ignition.java  |  74 ++--
 .../java/org/apache/ignite/IgnitionManager.java    |  90 ++---
 .../org/apache/ignite/cli/ItConfigCommandTest.java |  83 ++--
 .../ignite/client/handler/ItClientHandlerTest.java |   4 +-
 .../ignite/client/handler/ClientHandlerModule.java |  16 +-
 .../apache/ignite/client/AbstractClientTest.java   |   4 +-
 modules/cluster-management/pom.xml                 | 111 ++++++
 .../cluster/management/ClusterInitializer.java     | 143 +++++++
 .../management/ClusterManagementGroupManager.java  | 236 +++++++++++
 .../cluster/management/CmgRaftGroupListener.java   |  62 +++
 .../cluster/management/InitException.java}         |  23 +-
 .../management/messages/CancelInitMessage.java}    |  22 +-
 .../management/messages/ClusterStateMessage.java}  |  19 +-
 .../management/messages/CmgInitMessage.java}       |  19 +-
 .../management/messages/CmgMessageGroup.java       |  51 +++
 .../management/messages/InitCompleteMessage.java}  |  14 +-
 .../management/messages/InitErrorMessage.java}     |  18 +-
 .../cluster/management/rest/InitCommand.java       |  51 +++
 .../management/rest/InitCommandHandler.java        |  98 +++++
 .../cluster/management/ClusterInitializerTest.java | 192 +++++++++
 .../testframework/BaseIgniteAbstractTest.java      |  20 +-
 .../metastorage/client/MetaStorageService.java     |   1 -
 modules/metastorage/pom.xml                        |   5 +
 .../internal/metastorage/MetaStorageManager.java   | 432 +++++++--------------
 .../MessageDeserializerGenerator.java              |  42 +-
 .../serialization/MessageSerializerGenerator.java  |  28 +-
 .../ignite/network/util/ClusterServiceUtils.java   |  55 +++
 .../apache/ignite/internal/rest/api/Routes.java    |  13 +
 .../apache/ignite/internal/rest/RestComponent.java |  18 +
 modules/runner/pom.xml                             |   1 -
 .../ItDistributedConfigurationPropertiesTest.java  |  59 +--
 .../ItDistributedConfigurationStorageTest.java     |  54 +--
 .../runner/app/AbstractSchemaChangeTest.java       |  16 +-
 .../internal/runner/app/ItBaselineChangesTest.java |  44 +--
 .../internal/runner/app/ItClusterInitTest.java     | 102 +++++
 .../internal/runner/app/ItDataSchemaSyncTest.java  |  44 +--
 .../runner/app/ItDynamicTableCreationTest.java     |  97 ++---
 .../runner/app/ItIgniteNodeRestartTest.java        |   3 -
 .../ignite/internal/runner/app/ItIgnitionTest.java | 100 +----
 .../internal/runner/app/ItNoThreadsLeftTest.java   |  74 ++--
 .../runner/app/ItSchemaChangeKvViewTest.java       |  12 +-
 .../runner/app/ItSchemaChangeTableViewTest.java    |  14 +-
 .../internal/runner/app/ItTableCreationTest.java   |   3 -
 .../internal/runner/app/ItTablesApiTest.java       | 148 +++----
 .../runner/app/PlatformTestNodeRunner.java         |  23 +-
 .../app/client/ItAbstractThinClientTest.java       |  29 +-
 .../runner/app/jdbc/AbstractJdbcSelfTest.java      |  16 +-
 .../runner/app/jdbc/ItJdbcBatchSelfTest.java       |   2 +-
 .../sql/engine/AbstractBasicIntegrationTest.java   |  22 +-
 .../integrationTest/resources/ignite-config.json   |   5 -
 .../org/apache/ignite/internal/app/IgniteImpl.java | 134 ++++---
 .../CoreDistributedConfigurationModule.java        |   2 -
 .../CoreLocalConfigurationModule.java              |   2 -
 .../CoreDistributedConfigurationModuleTest.java    |   6 -
 .../CoreLocalConfigurationModuleTest.java          |   6 -
 parent/pom.xml                                     |  41 +-
 pom.xml                                            |   1 +
 65 files changed, 2005 insertions(+), 1313 deletions(-)

diff --git a/examples/config/ignite-config.json b/examples/config/ignite-config.json
index 6060004..8698d5a 100644
--- a/examples/config/ignite-config.json
+++ b/examples/config/ignite-config.json
@@ -1,9 +1,4 @@
 {
-    "node": {
-        "metastorageNodes": [
-            "my-first-node"
-        ]
-    },
     "network": {
         "port": 3344,
         "portRange": 10,
diff --git a/examples/pom.xml b/examples/pom.xml
index 5ed1dba..46dfb6d 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -34,6 +34,18 @@
 
     <properties>
         <root.directory>${pom.basedir}/..</root.directory>
+
+        <argLine>
+            --add-opens java.base/java.lang=ALL-UNNAMED
+            --add-opens java.base/java.lang.invoke=ALL-UNNAMED
+            --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+            --add-opens java.base/java.io=ALL-UNNAMED
+            --add-opens java.base/java.nio=ALL-UNNAMED
+            --add-opens java.base/java.util=ALL-UNNAMED
+            --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
+            -Dio.netty.tryReflectionSetAccessible=true
+            -Djava.util.logging.config.file=../config/java.util.logging.properties
+        </argLine>
     </properties>
 
     <dependencies>
diff --git a/examples/src/integrationTest/java/org/apache/ignite/example/AbstractExamplesTest.java b/examples/src/integrationTest/java/org/apache/ignite/example/AbstractExamplesTest.java
new file mode 100644
index 0000000..3765410
--- /dev/null
+++ b/examples/src/integrationTest/java/org/apache/ignite/example/AbstractExamplesTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.example;
+
+import java.nio.file.Path;
+import java.util.List;
+import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.testframework.IgniteAbstractTest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
+/**
+ * Base class for creating tests for examples.
+ */
+public abstract class AbstractExamplesTest extends IgniteAbstractTest {
+    private static final String TEST_NODE_NAME = "ignite-node";
+
+    /** Empty argument to invoke an example. */
+    protected static final String[] EMPTY_ARGS = new String[0];
+
+    /**
+     * Starts a node.
+     */
+    @BeforeEach
+    public void startNode() throws Exception {
+        IgniteImpl ignite = (IgniteImpl) IgnitionManager.start(
+                TEST_NODE_NAME,
+                Path.of("config", "ignite-config.json"),
+                workDir,
+                null
+        );
+
+        ignite.init(List.of(ignite.name()));
+    }
+
+    /**
+     * Stops the node.
+     */
+    @AfterEach
+    public void stopNode() {
+        IgnitionManager.stop(TEST_NODE_NAME);
+    }
+}
diff --git a/examples/src/test/java/org/apache/ignite/example/ExampleTestUtils.java b/examples/src/integrationTest/java/org/apache/ignite/example/ExampleTestUtils.java
similarity index 97%
rename from examples/src/test/java/org/apache/ignite/example/ExampleTestUtils.java
rename to examples/src/integrationTest/java/org/apache/ignite/example/ExampleTestUtils.java
index ec08af6..b8b4bb2 100644
--- a/examples/src/test/java/org/apache/ignite/example/ExampleTestUtils.java
+++ b/examples/src/integrationTest/java/org/apache/ignite/example/ExampleTestUtils.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
diff --git a/examples/src/test/java/org/apache/ignite/example/sql/jdbc/SqlExamplesTest.java b/examples/src/integrationTest/java/org/apache/ignite/example/sql/jdbc/ItSqlExamplesTest.java
similarity index 55%
rename from examples/src/test/java/org/apache/ignite/example/sql/jdbc/SqlExamplesTest.java
rename to examples/src/integrationTest/java/org/apache/ignite/example/sql/jdbc/ItSqlExamplesTest.java
index f6d9440..54cdecb 100644
--- a/examples/src/test/java/org/apache/ignite/example/sql/jdbc/SqlExamplesTest.java
+++ b/examples/src/integrationTest/java/org/apache/ignite/example/sql/jdbc/ItSqlExamplesTest.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
@@ -17,27 +17,14 @@
 
 package org.apache.ignite.example.sql.jdbc;
 
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.example.AbstractExamplesTest;
 import org.apache.ignite.example.ExampleTestUtils;
-import org.apache.ignite.internal.testframework.WorkDirectory;
-import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
-import org.apache.ignite.internal.util.IgniteUtils;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
 
 /**
  * These tests check that all SQL JDBC examples pass correctly.
  */
-@ExtendWith(WorkDirectoryExtension.class)
-public class SqlExamplesTest {
-    /** Empty argument to invoke an example. */
-    protected static final String[] EMPTY_ARGS = new String[0];
-
+public class ItSqlExamplesTest extends AbstractExamplesTest {
     /**
      * Runs SqlJdbcExample and checks its output.
      *
@@ -62,39 +49,4 @@ public class SqlExamplesTest {
                         + "    Richard, Miles, St. Petersburg\n"
         );
     }
-
-    /**
-     * Start node.
-     *
-     * @param workDir Work directory for the started node. Must not be {@code null}.
-     */
-    @BeforeEach
-    public void startNode(@WorkDirectory Path workDir) throws IOException {
-        IgnitionManager.start(
-                "my-first-node",
-                Files.readString(Path.of("config", "ignite-config.json")),
-                workDir
-        );
-    }
-
-    /**
-     * Stop node.
-     */
-    @AfterEach
-    public void stopNode() {
-        IgnitionManager.stop("my-first-node");
-    }
-
-    /**
-     * Removes a work directory created by {@link SqlExamplesTest}.
-     */
-    @BeforeEach
-    @AfterEach
-    public void removeWorkDir() {
-        Path workDir = Path.of("work");
-
-        if (Files.exists(workDir)) {
-            IgniteUtils.deleteIfExists(workDir);
-        }
-    }
 }
diff --git a/examples/src/test/java/org/apache/ignite/example/table/TableExamplesTest.java b/examples/src/integrationTest/java/org/apache/ignite/example/table/ItTableExamplesTest.java
similarity index 65%
rename from examples/src/test/java/org/apache/ignite/example/table/TableExamplesTest.java
rename to examples/src/integrationTest/java/org/apache/ignite/example/table/ItTableExamplesTest.java
index 5c179b5..5292b25 100644
--- a/examples/src/test/java/org/apache/ignite/example/table/TableExamplesTest.java
+++ b/examples/src/integrationTest/java/org/apache/ignite/example/table/ItTableExamplesTest.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
@@ -17,27 +17,14 @@
 
 package org.apache.ignite.example.table;
 
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.example.AbstractExamplesTest;
 import org.apache.ignite.example.ExampleTestUtils;
-import org.apache.ignite.internal.testframework.WorkDirectory;
-import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
-import org.apache.ignite.internal.util.IgniteUtils;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
 
 /**
  * These tests check that all table examples pass correctly.
  */
-@ExtendWith(WorkDirectoryExtension.class)
-public class TableExamplesTest {
-    /** Empty argument to invoke an example. */
-    protected static final String[] EMPTY_ARGS = new String[0];
-
+public class ItTableExamplesTest extends AbstractExamplesTest {
     /**
      * Runs RecordViewExample.
      *
@@ -93,39 +80,4 @@ public class TableExamplesTest {
                         + "    Owner: Val Kulichenko\n"
                         + "    Balance: $100.0\n");
     }
-
-    /**
-     * Start node.
-     *
-     * @param workDir Work directory for the started node. Must not be {@code null}.
-     */
-    @BeforeEach
-    public void startNode(@WorkDirectory Path workDir) throws IOException {
-        IgnitionManager.start(
-                "my-first-node",
-                Files.readString(Path.of("config", "ignite-config.json")),
-                workDir
-        );
-    }
-
-    /**
-     * Stop node.
-     */
-    @AfterEach
-    public void stopNode() {
-        IgnitionManager.stop("my-first-node");
-    }
-
-    /**
-     * Removes a previously created work directory.
-     */
-    @BeforeEach
-    @AfterEach
-    public void removeWorkDir() {
-        Path workDir = Path.of("work");
-
-        if (Files.exists(workDir)) {
-            IgniteUtils.deleteIfExists(workDir);
-        }
-    }
 }
diff --git a/examples/src/integrationTest/java/org/apache/ignite/example/tx/ItTransactionsExamplesTest.java b/examples/src/integrationTest/java/org/apache/ignite/example/tx/ItTransactionsExamplesTest.java
new file mode 100644
index 0000000..c6b3f2b
--- /dev/null
+++ b/examples/src/integrationTest/java/org/apache/ignite/example/tx/ItTransactionsExamplesTest.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.example.tx;
+
+import org.apache.ignite.example.AbstractExamplesTest;
+import org.apache.ignite.example.ExampleTestUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for transactional examples.
+ */
+public class ItTransactionsExamplesTest extends AbstractExamplesTest {
+    /**
+     * Runs TransactionsExample and checks its output.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testTransactionsExample() throws Exception {
+        ExampleTestUtils.assertConsoleOutputContains(TransactionsExample::main, EMPTY_ARGS,
+                "Initial balance: 1000.0",
+                "Balance after the sync transaction: 1200.0",
+                "Balance after the async transaction: 1500.0");
+    }
+}
diff --git a/examples/src/test/java/org/apache/ignite/example/tx/TransactionsExamplesTest.java b/examples/src/test/java/org/apache/ignite/example/tx/TransactionsExamplesTest.java
deleted file mode 100644
index 8a59175..0000000
--- a/examples/src/test/java/org/apache/ignite/example/tx/TransactionsExamplesTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.example.tx;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.apache.ignite.IgnitionManager;
-import org.apache.ignite.example.ExampleTestUtils;
-import org.apache.ignite.internal.testframework.WorkDirectory;
-import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
-import org.apache.ignite.internal.util.IgniteUtils;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-/**
- * Tests for transactional examples.
- */
-@ExtendWith(WorkDirectoryExtension.class)
-public class TransactionsExamplesTest {
-    /** Empty argument to invoke an example. */
-    protected static final String[] EMPTY_ARGS = new String[0];
-
-    /**
-     * Runs TransactionsExample and checks its output.
-     *
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testTransactionsExample() throws Exception {
-        ExampleTestUtils.assertConsoleOutputContains(TransactionsExample::main, EMPTY_ARGS,
-                "Initial balance: 1000.0",
-                "Balance after the sync transaction: 1200.0",
-                "Balance after the async transaction: 1500.0");
-    }
-
-    /**
-     * Start node.
-     *
-     * @param workDir Work directory for the started node. Must not be {@code null}.
-     */
-    @BeforeEach
-    public void startNode(@WorkDirectory Path workDir) throws IOException {
-        IgnitionManager.start(
-                "my-first-node",
-                Files.readString(Path.of("config", "ignite-config.json")),
-                workDir
-        );
-    }
-
-    /**
-     * Stop node.
-     */
-    @AfterEach
-    public void stopNode() {
-        IgnitionManager.stop("my-first-node");
-    }
-
-    /**
-     * Removes a previously created work directory.
-     */
-    @BeforeEach
-    @AfterEach
-    public void removeWorkDir() {
-        Path workDir = Path.of("work");
-
-        if (Files.exists(workDir)) {
-            IgniteUtils.deleteIfExists(workDir);
-        }
-    }
-}
diff --git a/modules/api/src/main/java/org/apache/ignite/Ignition.java b/modules/api/src/main/java/org/apache/ignite/Ignition.java
index 4824fb6..0585353 100644
--- a/modules/api/src/main/java/org/apache/ignite/Ignition.java
+++ b/modules/api/src/main/java/org/apache/ignite/Ignition.java
@@ -20,7 +20,6 @@ package org.apache.ignite;
 import java.io.InputStream;
 import java.net.URL;
 import java.nio.file.Path;
-import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -31,70 +30,69 @@ public interface Ignition {
     /**
      * Starts an Ignite node with an optional bootstrap configuration from a HOCON file.
      *
-     * @param name       Name of the node. Must not be {@code null}.
+     * @param name Name of the node. Must not be {@code null}.
      * @param configPath Path to the node configuration in the HOCON format. Can be {@code null}.
-     * @param workDir    Work directory for the started node. Must not be {@code null}.
+     * @param workDir Work directory for the started node. Must not be {@code null}.
      * @return Started Ignite node.
      */
-    public Ignite start(@NotNull String name, @Nullable Path configPath, @NotNull Path workDir);
+    public Ignite start(String name, @Nullable Path configPath, Path workDir);
 
     /**
-     * Starts an Ignite node with an optional bootstrap configuration from a HOCON file, with an optional
-     * class loader for further usage by {@link java.util.ServiceLoader}.
+     * Starts an Ignite node with an optional bootstrap configuration from a HOCON file, with an optional class loader for further usage by
+     * {@link java.util.ServiceLoader}.
      *
-     * @param name                     Name of the node. Must not be {@code null}.
-     * @param configPath               Path to the node configuration in the HOCON format. Can be {@code null}.
-     * @param workDir                  Work directory for the started node. Must not be {@code null}.
-     * @param serviceLoaderClassLoader The class loader to be used to load provider-configuration files and provider
-     *                                 classes, or {@code null} if the system
-     *                                 class loader (or, failing that, the bootstrap class loader) is to be used
+     * @param name Name of the node. Must not be {@code null}.
+     * @param configPath Path to the node configuration in the HOCON format. Can be {@code null}.
+     * @param workDir Work directory for the started node. Must not be {@code null}.
+     * @param serviceLoaderClassLoader The class loader to be used to load provider-configuration files and provider classes, or {@code
+     * null} if the system class loader (or, failing that, the bootstrap class loader) is to be used
      * @return Started Ignite node.
      */
-    public Ignite start(@NotNull String name, @Nullable Path configPath, @NotNull Path workDir,
-                        @Nullable ClassLoader serviceLoaderClassLoader);
+    public Ignite start(String name, @Nullable Path configPath, Path workDir, @Nullable ClassLoader serviceLoaderClassLoader);
 
     /**
      * Starts an Ignite node with an optional bootstrap configuration from a URL linking to HOCON configs.
      *
-     * @param name       Name of the node. Must not be {@code null}.
-     * @param cfgUrl     URL linking to the node configuration in the HOCON format. Can be {@code null}.
-     * @param workDir    Work directory for the started node. Must not be {@code null}.
+     * @param name Name of the node. Must not be {@code null}.
+     * @param cfgUrl URL linking to the node configuration in the HOCON format. Can be {@code null}.
+     * @param workDir Work directory for the started node. Must not be {@code null}.
      * @return Started Ignite node.
      */
-    public Ignite start(@NotNull String name, @Nullable URL cfgUrl, @NotNull Path workDir);
+    public Ignite start(String name, @Nullable URL cfgUrl, Path workDir);
 
     /**
      * Starts an Ignite node with an optional bootstrap configuration from an input stream with HOCON configs.
      *
-     * @param name    Name of the node. Must not be {@code null}.
-     * @param config  Optional node configuration based on {@link org.apache.ignite.configuration.schemas.runner.NodeConfigurationSchema}
-     *                and {@link org.apache.ignite.configuration.schemas.network.NetworkConfigurationSchema}. Following rules are used for
-     *                applying the configuration properties:
-     *                <ol>
-     *                  <li>Specified property overrides existing one or just applies itself if it wasn't
-     *                      previously specified.</li>
-     *                  <li>All non-specified properties either use previous value or use default one from
-     *                      corresponding configuration schema.</li>
-     *                </ol>
-     *                So that, in case of initial node start (first start ever) specified configuration, supplemented
-     *                with defaults, is used. If no configuration was provided defaults are used for all
-     *                configuration properties. In case of node restart, specified properties override existing
-     *                ones, non specified properties that also weren't specified previously use default values.
-     *                Please pay attention that previously specified properties are searched in the
-     *                {@code workDir} specified by the user.
+     * @param name Name of the node. Must not be {@code null}.
+     * @param config Optional node configuration based on
+     *      {@link org.apache.ignite.configuration.schemas.network.NetworkConfigurationSchema}.
+     *      Following rules are used for applying the configuration properties:
+     *      <ol>
+     *        <li>Specified property overrides existing one or just applies itself if it wasn't
+     *            previously specified.</li>
+     *        <li>All non-specified properties either use previous value or use default one from
+     *            corresponding configuration schema.</li>
+     *      </ol>
+     *      So that, in case of initial node start (first start ever) specified configuration, supplemented
+     *      with defaults, is used. If no configuration was provided defaults are used for all
+     *      configuration properties. In case of node restart, specified properties override existing
+     *      ones, non specified properties that also weren't specified previously use default values.
+     *      Please pay attention that previously specified properties are searched in the
+     *      {@code workDir} specified by the user.
+     *
      * @param workDir Work directory for the started node. Must not be {@code null}.
      * @return Started Ignite node.
      */
-    public Ignite start(@NotNull String name, @Nullable InputStream config, @NotNull Path workDir);
+    public Ignite start(String name, @Nullable InputStream config, Path workDir);
 
     /**
      * Starts an Ignite node with the default configuration.
      *
-     * @param name    Name of the node. Must not be {@code null}.
+     * @param name Name of the node. Must not be {@code null}.
      * @param workDir Work directory for the started node. Must not be {@code null}.
      * @return Started Ignite node.
      */
-    public Ignite start(@NotNull String name, @NotNull Path workDir);
+    public Ignite start(String name, Path workDir);
 
     /**
      * Stops the node with given {@code name}. It's possible to stop both already started node or node that is currently starting. Has no
@@ -103,5 +101,5 @@ public interface Ignition {
      * @param name Node name to stop.
      * @throws IllegalArgumentException if null is specified instead of node name.
      */
-    public void stop(@NotNull String name);
+    public void stop(String name);
 }
diff --git a/modules/api/src/main/java/org/apache/ignite/IgnitionManager.java b/modules/api/src/main/java/org/apache/ignite/IgnitionManager.java
index 84074f6..1896e5e 100644
--- a/modules/api/src/main/java/org/apache/ignite/IgnitionManager.java
+++ b/modules/api/src/main/java/org/apache/ignite/IgnitionManager.java
@@ -24,7 +24,6 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.ServiceLoader;
 import org.apache.ignite.lang.IgniteException;
-import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -36,40 +35,32 @@ public class IgnitionManager {
     private static Ignition ignition;
 
     /**
-     * Starts an Ignite node with an optional bootstrap configuration from a HOCON file.
+     * Starts an Ignite node with an optional bootstrap configuration from an input stream with HOCON configs.
+     *
+     * @param nodeName Name of the node. Must not be {@code null}.
+     * @param configStr Optional node configuration based on
+     *      {@link org.apache.ignite.configuration.schemas.network.NetworkConfigurationSchema}.
+     *      Following rules are used for applying the configuration properties:
+     *      <ol>
+     *        <li>Specified property overrides existing one or just applies itself if it wasn't
+     *            previously specified.</li>
+     *        <li>All non-specified properties either use previous value or use default one from
+     *            corresponding configuration schema.</li>
+     *      </ol>
+     *      So that, in case of initial node start (first start ever) specified configuration, supplemented
+     *      with defaults, is used. If no configuration was provided defaults are used for all
+     *      configuration properties. In case of node restart, specified properties override existing
+     *      ones, non specified properties that also weren't specified previously use default values.
+     *      Please pay attention that previously specified properties are searched in the
+     *      {@code workDir} specified by the user.
      *
-     * @param nodeName  Name of the node. Must not be {@code null}.
-     * @param configStr Optional node configuration based on {@link org.apache.ignite.configuration.schemas.runner.NodeConfigurationSchema}
-     *                  and {@link org.apache.ignite.configuration.schemas.network.NetworkConfigurationSchema}. Following rules are used for
-     *                  applying the configuration properties:
-     *                  <ol>
-     *                      <li>Specified property overrides existing one or just applies itself if it wasn't
-     *                          previously specified.</li>
-     *                      <li>All non-specified properties either use previous value or use default one from
-     *                          corresponding configuration schema.</li>
-     *                  </ol>
-     *                  So that, in case of initial node start (first start ever) specified configuration, supplemented
-     *                  with defaults, is used. If no configuration was provided defaults are used for all
-     *                  configuration properties. In case of node restart, specified properties override existing
-     *                  ones, non specified properties that also weren't specified previously use default values.
-     *                  Please pay attention that previously specified properties are searched in the
-     *                  {@code workDir} specified by the user.
-     * @param workDir   Work directory for the started node. Must not be {@code null}.
+     * @param workDir Work directory for the started node. Must not be {@code null}.
      * @return Started Ignite node.
      * @throws IgniteException If error occurs while reading node configuration.
      */
     // TODO IGNITE-14580 Add exception handling logic to IgnitionProcessor.
-    public static Ignite start(
-            @NotNull String nodeName,
-            @Nullable String configStr,
-            @NotNull Path workDir
-    ) {
-        synchronized (IgnitionManager.class) {
-            if (ignition == null) {
-                ServiceLoader<Ignition> ldr = ServiceLoader.load(Ignition.class);
-                ignition = ldr.iterator().next();
-            }
-        }
+    public static Ignite start(String nodeName, @Nullable String configStr, Path workDir) {
+        loadIgnitionService(Thread.currentThread().getContextClassLoader());
 
         if (configStr == null) {
             return ignition.start(nodeName, workDir);
@@ -93,18 +84,8 @@ public class IgnitionManager {
      * @return Started Ignite node.
      */
     // TODO IGNITE-14580 Add exception handling logic to IgnitionProcessor.
-    public static Ignite start(
-            @NotNull String nodeName,
-            @Nullable Path cfgPath,
-            @NotNull Path workDir,
-            @Nullable ClassLoader clsLdr
-    ) {
-        synchronized (IgnitionManager.class) {
-            if (ignition == null) {
-                ServiceLoader<Ignition> ldr = ServiceLoader.load(Ignition.class, clsLdr);
-                ignition = ldr.iterator().next();
-            }
-        }
+    public static Ignite start(String nodeName, @Nullable Path cfgPath, Path workDir, @Nullable ClassLoader clsLdr) {
+        loadIgnitionService(clsLdr);
 
         return ignition.start(nodeName, cfgPath, workDir, clsLdr);
     }
@@ -116,13 +97,8 @@ public class IgnitionManager {
      * @param name Node name to stop.
      * @throws IllegalArgumentException if null is specified instead of node name.
      */
-    public static void stop(@NotNull String name) {
-        synchronized (IgnitionManager.class) {
-            if (ignition == null) {
-                ServiceLoader<Ignition> ldr = ServiceLoader.load(Ignition.class);
-                ignition = ldr.iterator().next();
-            }
-        }
+    public static void stop(String name) {
+        loadIgnitionService(Thread.currentThread().getContextClassLoader());
 
         ignition.stop(name);
     }
@@ -136,14 +112,16 @@ public class IgnitionManager {
      *               class loader (or, failing that, the bootstrap class loader) is to be used
      * @throws IllegalArgumentException if null is specified instead of node name.
      */
-    public static void stop(@NotNull String name, @Nullable ClassLoader clsLdr) {
-        synchronized (IgnitionManager.class) {
-            if (ignition == null) {
-                ServiceLoader<Ignition> ldr = ServiceLoader.load(Ignition.class, clsLdr);
-                ignition = ldr.iterator().next();
-            }
-        }
+    public static void stop(String name, @Nullable ClassLoader clsLdr) {
+        loadIgnitionService(clsLdr);
 
         ignition.stop(name);
     }
+
+    private static synchronized void loadIgnitionService(@Nullable ClassLoader clsLdr) {
+        if (ignition == null) {
+            ServiceLoader<Ignition> ldr = ServiceLoader.load(Ignition.class, clsLdr);
+            ignition = ldr.iterator().next();
+        }
+    }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
index 0edb906..d594e05 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
@@ -32,74 +32,50 @@ import com.jayway.jsonpath.JsonPath;
 import io.micronaut.context.ApplicationContext;
 import io.micronaut.context.env.Environment;
 import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.PrintWriter;
-import java.net.ServerSocket;
 import java.nio.file.Path;
 import net.minidev.json.JSONObject;
 import net.minidev.json.JSONValue;
-import org.apache.ignite.Ignite;
 import org.apache.ignite.IgnitionManager;
 import org.apache.ignite.cli.spec.IgniteCliSpec;
+import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
-import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.api.extension.ExtendWith;
 import picocli.CommandLine;
 
 /**
  * Integration test for {@code ignite config} commands.
  */
+@ExtendWith(WorkDirectoryExtension.class)
 public class ItConfigCommandTest extends AbstractCliTest {
     /** DI context. */
     private ApplicationContext ctx;
 
     /** stderr. */
-    private ByteArrayOutputStream err;
+    private final ByteArrayOutputStream err = new ByteArrayOutputStream();
 
     /** stdout. */
-    private ByteArrayOutputStream out;
-
-    /** Port for REST communication. */
-    private int restPort;
-
-    /** Port for thin client communication. */
-    private int clientPort;
-
-    /** Network port. */
-    private int networkPort;
+    private final ByteArrayOutputStream out = new ByteArrayOutputStream();
 
     /** Node. */
-    private Ignite node;
+    private IgniteImpl node;
 
     @BeforeEach
-    void setup(@TempDir Path workDir, TestInfo testInfo) throws IOException {
-        // TODO: IGNITE-15131 Must be replaced by receiving the actual port configs from the started node.
-        // This approach still can produce the port, which will be unavailable at the moment of node start.
-        restPort = getAvailablePort();
-        clientPort = getAvailablePort();
-        networkPort = getAvailablePort();
-
-        String configStr = String.join("\n",
-                "network.port=" + networkPort,
-                "rest.port=" + restPort,
-                "rest.portRange=0",
-                "clientConnector.port=" + clientPort,
-                "clientConnector.portRange=0"
-        );
-
-        this.node = IgnitionManager.start(testNodeName(testInfo, networkPort), configStr, workDir);
+    void setup(@WorkDirectory Path workDir, TestInfo testInfo) {
+        node = (IgniteImpl) IgnitionManager.start(testNodeName(testInfo, 0), null, workDir);
 
         ctx = ApplicationContext.run(Environment.TEST);
-
-        err = new ByteArrayOutputStream();
-        out = new ByteArrayOutputStream();
     }
 
     @AfterEach
     void tearDown(TestInfo testInfo) {
-        IgnitionManager.stop(testNodeName(testInfo, networkPort));
+        node.stop();
+
         ctx.stop();
     }
 
@@ -109,9 +85,9 @@ public class ItConfigCommandTest extends AbstractCliTest {
                 "config",
                 "set",
                 "--node-endpoint",
-                "localhost:" + restPort,
+                "localhost:" + node.restAddress().port(),
                 "--type", "node", //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
-                "node.metastorageNodes=[\"localhost1\"]"
+                "network.shutdownQuietPeriod=1"
         );
 
         String nl = System.lineSeparator();
@@ -129,7 +105,7 @@ public class ItConfigCommandTest extends AbstractCliTest {
                 "config",
                 "get",
                 "--node-endpoint",
-                "localhost:" + restPort,
+                "localhost:" + node.restAddress().port(),
                 "--type", "node" //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
         );
 
@@ -137,7 +113,7 @@ public class ItConfigCommandTest extends AbstractCliTest {
 
         DocumentContext document = JsonPath.parse(removeTrailingQuotes(unescapeQuotes(out.toString(UTF_8))));
 
-        assertEquals("localhost1", document.read("$.node.metastorageNodes[0]"));
+        assertEquals(1, document.read("$.network.shutdownQuietPeriod", Integer.class));
     }
 
     @Test
@@ -146,16 +122,16 @@ public class ItConfigCommandTest extends AbstractCliTest {
                 "config",
                 "set",
                 "--node-endpoint",
-                "localhost:" + restPort,
+                "localhost:" + node.restAddress().port(),
                 "--type", "node", //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
-                "node.metastorgeNodes=[\"localhost1\"]"
+                "network.foo=\"bar\""
         );
 
         assertEquals(1, exitCode);
         assertThat(
                 err.toString(UTF_8),
                 both(startsWith("org.apache.ignite.cli.IgniteCliException: Failed to set configuration"))
-                        .and(containsString("'node' configuration doesn't have the 'metastorgeNodes' sub-configuration"))
+                        .and(containsString("'network' configuration doesn't have the 'foo' sub-configuration"))
         );
 
         resetStreams();
@@ -164,16 +140,16 @@ public class ItConfigCommandTest extends AbstractCliTest {
                 "config",
                 "set",
                 "--node-endpoint",
-                "localhost:" + restPort,
+                "localhost:" + node.restAddress().port(),
                 "--type", "node", //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
-                "node.metastorageNodes=abc"
+                "network.shutdownQuietPeriod=abc"
         );
 
         assertEquals(1, exitCode);
         assertThat(
                 err.toString(UTF_8),
                 both(startsWith("org.apache.ignite.cli.IgniteCliException: Failed to set configuration"))
-                        .and(containsString("'String[]' is expected as a type for the 'node.metastorageNodes' configuration value"))
+                        .and(containsString("'long' is expected as a type for the 'network.shutdownQuietPeriod' configuration value"))
         );
     }
 
@@ -183,7 +159,7 @@ public class ItConfigCommandTest extends AbstractCliTest {
                 "config",
                 "get",
                 "--node-endpoint",
-                "localhost:" + restPort,
+                "localhost:" + node.restAddress().port(),
                 "--selector",
                 "network",
                 "--type", "node" //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
@@ -199,19 +175,6 @@ public class ItConfigCommandTest extends AbstractCliTest {
     }
 
     /**
-     * Returns any available prt.
-     *
-     * @return Any available port.
-     * @throws IOException if can't allocate port to open socket.
-     */
-    // TODO: Must be removed after IGNITE-15131.
-    private static int getAvailablePort() throws IOException {
-        ServerSocket s = new ServerSocket(0);
-        s.close();
-        return s.getLocalPort();
-    }
-
-    /**
      * Creates a new command line interpreter.
      *
      * @param applicationCtx DI context.
diff --git a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
index 93fe8db..38eef11 100644
--- a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
+++ b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
@@ -25,11 +25,9 @@ import static org.mockito.Mockito.mock;
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import org.apache.ignite.configuration.schemas.clientconnector.ClientConnectorConfiguration;
 import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
 import org.apache.ignite.internal.configuration.ConfigurationManager;
@@ -62,7 +60,7 @@ public class ItClientHandlerTest {
     @BeforeEach
     public void setUp(TestInfo testInfo) {
         serverModule = startServer(testInfo);
-        serverPort = ((InetSocketAddress) Objects.requireNonNull(serverModule.localAddress())).getPort();
+        serverPort = serverModule.localAddress().getPort();
     }
 
     @AfterEach
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
index 41c2c9e..f2893a3 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
@@ -23,18 +23,18 @@ import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
 import java.net.BindException;
-import java.net.SocketAddress;
+import java.net.InetSocketAddress;
 import org.apache.ignite.configuration.schemas.clientconnector.ClientConnectorConfiguration;
 import org.apache.ignite.internal.client.proto.ClientMessageDecoder;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.network.NettyBootstrapFactory;
 import org.apache.ignite.table.manager.IgniteTables;
 import org.apache.ignite.tx.IgniteTransactions;
-import org.jetbrains.annotations.Nullable;
 
 /**
  * Client handler module maintains TCP endpoint for thin client connections.
@@ -115,11 +115,15 @@ public class ClientHandlerModule implements IgniteComponent {
     /**
      * Returns the local address where this handler is bound to.
      *
-     * @return the local address of this module, or {@code null} if this module is not started.
+     * @return the local address of this module.
+     * @throws IgniteInternalException if the module is not started.
      */
-    @Nullable
-    public SocketAddress localAddress() {
-        return channel == null ? null : channel.localAddress();
+    public InetSocketAddress localAddress() {
+        if (channel == null) {
+            throw new IgniteInternalException("ClientHandlerModule has not been started");
+        }
+
+        return (InetSocketAddress) channel.localAddress();
     }
 
     /**
diff --git a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
index 673516c..1f8fa05 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
@@ -21,8 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
 import io.netty.util.ResourceLeakDetector;
-import java.net.InetSocketAddress;
-import java.util.Objects;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.client.fakes.FakeIgnite;
 import org.apache.ignite.client.handler.ClientHandlerModule;
@@ -139,6 +137,6 @@ public abstract class AbstractClientTest {
     }
 
     public static int getPort(ClientHandlerModule hnd) {
-        return ((InetSocketAddress) Objects.requireNonNull(hnd.localAddress())).getPort();
+        return hnd.localAddress().getPort();
     }
 }
diff --git a/modules/cluster-management/pom.xml b/modules/cluster-management/pom.xml
new file mode 100644
index 0000000..b38061f
--- /dev/null
+++ b/modules/cluster-management/pom.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements. See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ignite</groupId>
+        <artifactId>ignite-parent</artifactId>
+        <version>1</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>ignite-cluster-management</artifactId>
+    <version>3.0.0-SNAPSHOT</version>
+
+    <dependencies>
+        <dependency>
+          <groupId>org.apache.ignite</groupId>
+          <artifactId>ignite-core</artifactId>
+        </dependency>
+
+        <dependency>
+          <groupId>org.apache.ignite</groupId>
+          <artifactId>ignite-network-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-raft</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-rest</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-core</artifactId>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.ignite</groupId>
+                        <artifactId>ignite-network-annotation-processor</artifactId>
+                        <version>${project.version}</version>
+                    </dependency>
+                </dependencies>
+                <configuration>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.apache.ignite</groupId>
+                            <artifactId>ignite-network-annotation-processor</artifactId>
+                            <version>${project.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java
new file mode 100644
index 0000000..4c03d0d
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management;
+
+import static org.apache.ignite.network.util.ClusterServiceUtils.resolveNodes;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import org.apache.ignite.internal.cluster.management.messages.CancelInitMessage;
+import org.apache.ignite.internal.cluster.management.messages.CmgInitMessage;
+import org.apache.ignite.internal.cluster.management.messages.CmgMessagesFactory;
+import org.apache.ignite.internal.cluster.management.messages.InitCompleteMessage;
+import org.apache.ignite.internal.cluster.management.messages.InitErrorMessage;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.network.ClusterNode;
+import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.NetworkMessage;
+
+/**
+ * Class for performing cluster initialization.
+ */
+public class ClusterInitializer {
+    private static final IgniteLogger log = IgniteLogger.forClass(ClusterInitializer.class);
+
+    private final ClusterService clusterService;
+
+    private final CmgMessagesFactory msgFactory = new CmgMessagesFactory();
+
+    /** Constructor. */
+    ClusterInitializer(ClusterService clusterService) {
+        this.clusterService = clusterService;
+    }
+
+    /**
+     * Initializes the cluster that this node is present in.
+     *
+     * @param metaStorageNodeNames names of nodes that will host the Meta Storage. Cannot be empty.
+     * @param cmgNodeNames names of nodes that will host the Cluster Management Group. Can be empty, in which case {@code
+     * metaStorageNodeNames} will be used instead.
+     * @return future that resolves into leader node IDs if completed successfully.
+     */
+    public CompletableFuture<Void> initCluster(Collection<String> metaStorageNodeNames, Collection<String> cmgNodeNames) {
+        try {
+            if (metaStorageNodeNames.isEmpty()) {
+                throw new IllegalArgumentException("List of metastorage nodes must not be empty");
+            }
+
+            cmgNodeNames = cmgNodeNames.isEmpty() ? metaStorageNodeNames : cmgNodeNames;
+
+            // check that provided Meta Storage nodes are present in the topology
+            resolveNodes(clusterService, metaStorageNodeNames);
+
+            List<ClusterNode> cmgNodes = resolveNodes(clusterService, cmgNodeNames);
+
+            CmgInitMessage initMessage = msgFactory.cmgInitMessage()
+                    .metaStorageNodes(metaStorageNodeNames)
+                    .cmgNodes(cmgNodeNames)
+                    .build();
+
+            return invokeMessage(cmgNodes, initMessage)
+                    .thenApply(CompletableFuture::completedFuture)
+                    .exceptionally(e -> cancelInit(cmgNodes, e))
+                    .thenCompose(Function.identity());
+        } catch (Exception e) {
+            return CompletableFuture.failedFuture(e);
+        }
+    }
+
+    private CompletableFuture<Void> cancelInit(Collection<ClusterNode> nodes, Throwable e) {
+        log.error("Initialization failed, rolling back", e);
+
+        CancelInitMessage cancelMessage = msgFactory.cancelInitMessage()
+                .reason(e.getMessage())
+                .build();
+
+        return sendMessage(nodes, cancelMessage)
+                .exceptionally(nestedEx -> {
+                    log.error("Error when canceling init", nestedEx);
+
+                    e.addSuppressed(nestedEx);
+
+                    return null;
+                })
+                .thenCompose(v -> CompletableFuture.failedFuture(e));
+    }
+
+    /**
+     * Sends a message to all provided nodes.
+     *
+     * @param nodes nodes to send message to.
+     * @param message message to send.
+     * @return future that either resolves to a leader node ID or fails if any of the nodes return an error response.
+     */
+    private CompletableFuture<Void> invokeMessage(Collection<ClusterNode> nodes, NetworkMessage message) {
+        return allOf(nodes, node ->
+                clusterService.messagingService()
+                        .invoke(node, message, 10000)
+                        .thenAccept(response -> {
+                            if (response instanceof InitErrorMessage) {
+                                throw new InitException(String.format(
+                                        "Got error response from node \"%s\": %s", node.name(), ((InitErrorMessage) response).cause()
+                                ));
+                            }
+
+                            if (!(response instanceof InitCompleteMessage)) {
+                                throw new InitException(String.format(
+                                        "Unexpected response from node \"%s\": %s", node.name(), response.getClass()
+                                ));
+                            }
+                        })
+        );
+    }
+
+    private CompletableFuture<Void> sendMessage(Collection<ClusterNode> nodes, NetworkMessage message) {
+        return allOf(nodes, node -> clusterService.messagingService().send(node, message));
+    }
+
+    private static CompletableFuture<Void> allOf(
+            Collection<ClusterNode> nodes,
+            Function<ClusterNode, CompletableFuture<?>> futureProducer
+    ) {
+        CompletableFuture<?>[] futures = nodes.stream().map(futureProducer).toArray(CompletableFuture[]::new);
+
+        return CompletableFuture.allOf(futures);
+    }
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java
new file mode 100644
index 0000000..c22788c
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management;
+
+import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
+import static org.apache.ignite.network.util.ClusterServiceUtils.resolveNodes;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.ignite.internal.cluster.management.messages.CancelInitMessage;
+import org.apache.ignite.internal.cluster.management.messages.ClusterStateMessage;
+import org.apache.ignite.internal.cluster.management.messages.CmgInitMessage;
+import org.apache.ignite.internal.cluster.management.messages.CmgMessageGroup;
+import org.apache.ignite.internal.cluster.management.messages.CmgMessagesFactory;
+import org.apache.ignite.internal.cluster.management.rest.InitCommandHandler;
+import org.apache.ignite.internal.manager.IgniteComponent;
+import org.apache.ignite.internal.raft.Loza;
+import org.apache.ignite.internal.rest.RestComponent;
+import org.apache.ignite.internal.util.IgniteSpinBusyLock;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.lang.NodeStoppingException;
+import org.apache.ignite.network.ClusterNode;
+import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessagingService;
+import org.apache.ignite.network.NetworkAddress;
+import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.raft.client.Peer;
+import org.apache.ignite.raft.client.service.RaftGroupService;
+
+/**
+ * Ignite component responsible for cluster initialization and managing the Cluster Management Raft Group.
+ */
+public class ClusterManagementGroupManager implements IgniteComponent {
+    private static final IgniteLogger log = IgniteLogger.forClass(ClusterManagementGroupManager.class);
+
+    /** CMG Raft group name. */
+    private static final String CMG_RAFT_GROUP_NAME = "cmg_raft_group";
+
+    /** Init REST endpoint path. */
+    private static final String REST_ENDPOINT = "/management/v1/cluster/init";
+
+    /** Busy lock to stop synchronously. */
+    private final IgniteSpinBusyLock busyLock = new IgniteSpinBusyLock();
+
+    /** Prevents double stopping the component. */
+    private final AtomicBoolean stopGuard = new AtomicBoolean();
+
+    private final ClusterService clusterService;
+
+    private final Loza raftManager;
+
+    private final RestComponent restModule;
+
+    /** Handles cluster initialization flow. */
+    private final ClusterInitializer clusterInitializer;
+
+    private final CmgMessagesFactory msgFactory = new CmgMessagesFactory();
+
+    private final CompletableFuture<Collection<String>> metastorageNodes = new CompletableFuture<>();
+
+    /** Constructor. */
+    public ClusterManagementGroupManager(ClusterService clusterService, Loza raftManager, RestComponent restModule) {
+        this.clusterService = clusterService;
+        this.raftManager = raftManager;
+        this.restModule = restModule;
+        this.clusterInitializer = new ClusterInitializer(clusterService);
+
+        MessagingService messagingService = clusterService.messagingService();
+
+        messagingService.addMessageHandler(CmgMessageGroup.class, (msg, addr, correlationId) -> {
+            if (!busyLock.enterBusy()) {
+                if (correlationId != null) {
+                    messagingService.respond(addr, errorResponse(msgFactory, new NodeStoppingException()), correlationId);
+                }
+
+                return;
+            }
+
+            try {
+                if (msg instanceof CancelInitMessage) {
+                    handleCancelInit((CancelInitMessage) msg);
+                } else if (msg instanceof CmgInitMessage) {
+                    assert correlationId != null;
+
+                    handleInit((CmgInitMessage) msg, addr, correlationId);
+                } else if (msg instanceof ClusterStateMessage) {
+                    handleClusterState((ClusterStateMessage) msg);
+                }
+            } catch (Exception e) {
+                log.error("CMG message handling failed", e);
+
+                if (correlationId != null) {
+                    messagingService.respond(addr, errorResponse(msgFactory, e), correlationId);
+                }
+            } finally {
+                busyLock.leaveBusy();
+            }
+        });
+    }
+
+    /**
+     * Initializes the cluster that this node is present in.
+     *
+     * @param metaStorageNodeNames names of nodes that will host the Meta Storage.
+     * @param cmgNodeNames names of nodes that will host the Cluster Management Group.
+     */
+    public void initCluster(Collection<String> metaStorageNodeNames, Collection<String> cmgNodeNames) throws NodeStoppingException {
+        if (!busyLock.enterBusy()) {
+            throw new NodeStoppingException();
+        }
+
+        try {
+            clusterInitializer.initCluster(metaStorageNodeNames, cmgNodeNames).get();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+
+            throw new IgniteInternalException("Interrupted while initializing the cluster", e);
+        } catch (ExecutionException e) {
+            throw new IgniteInternalException("Unable to initialize the cluster", e.getCause());
+        } finally {
+            busyLock.leaveBusy();
+        }
+    }
+
+    private void handleInit(CmgInitMessage msg, NetworkAddress addr, long correlationId) throws NodeStoppingException {
+        List<ClusterNode> nodes = resolveNodes(clusterService, msg.cmgNodes());
+
+        raftManager.prepareRaftGroup(CMG_RAFT_GROUP_NAME, nodes, CmgRaftGroupListener::new)
+                .whenComplete((service, e) -> {
+                    MessagingService messagingService = clusterService.messagingService();
+
+                    if (e == null) {
+                        ClusterNode leader = getLeader(service);
+
+                        ClusterNode thisNode = clusterService.topologyService().localMember();
+
+                        messagingService.respond(addr, successResponse(msgFactory), correlationId);
+
+                        if (leader.equals(thisNode)) {
+                            broadcastClusterState(msg.metaStorageNodes());
+                        }
+                    } else {
+                        messagingService.respond(addr, errorResponse(msgFactory, e), correlationId);
+                    }
+                });
+    }
+
+    private void handleCancelInit(CancelInitMessage msg) throws NodeStoppingException {
+        log.info("CMG initialization cancelled, reason: " + msg.reason());
+
+        raftManager.stopRaftGroup(CMG_RAFT_GROUP_NAME);
+
+        // TODO: drop the Raft storage as well, https://issues.apache.org/jira/browse/IGNITE-16471
+    }
+
+    private void handleClusterState(ClusterStateMessage msg) {
+        metastorageNodes.complete(msg.metastorageNodes());
+    }
+
+    private static NetworkMessage successResponse(CmgMessagesFactory msgFactory) {
+        log.info("CMG started successfully");
+
+        return msgFactory.initCompleteMessage().build();
+    }
+
+    private void broadcastClusterState(Collection<String> metaStorageNodes) {
+        NetworkMessage clusterStateMsg = msgFactory.clusterStateMessage()
+                .metastorageNodes(metaStorageNodes)
+                .build();
+
+        clusterService.topologyService()
+                .allMembers()
+                .forEach(node -> clusterService.messagingService().send(node, clusterStateMsg));
+    }
+
+    private static NetworkMessage errorResponse(CmgMessagesFactory msgFactory, Throwable e) {
+        log.error("Exception when starting the CMG", e);
+
+        return msgFactory.initErrorMessage()
+                .cause(e.getMessage())
+                .build();
+    }
+
+    private ClusterNode getLeader(RaftGroupService raftService) {
+        Peer leader = raftService.leader();
+
+        assert leader != null;
+
+        ClusterNode leaderNode = clusterService.topologyService().getByAddress(leader.address());
+
+        assert leaderNode != null;
+
+        return leaderNode;
+    }
+
+    @Override
+    public void start() {
+        restModule.registerHandlers(routes ->
+                routes.post(REST_ENDPOINT, APPLICATION_JSON.toString(), new InitCommandHandler(clusterInitializer))
+        );
+    }
+
+    @Override
+    public void stop() throws Exception {
+        if (!stopGuard.compareAndSet(false, true)) {
+            return;
+        }
+
+        busyLock.block();
+
+        raftManager.stopRaftGroup(CMG_RAFT_GROUP_NAME);
+    }
+
+    public CompletableFuture<Collection<String>> metaStorageNodes() {
+        return metastorageNodes;
+    }
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/CmgRaftGroupListener.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/CmgRaftGroupListener.java
new file mode 100644
index 0000000..1a5f6b2
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/CmgRaftGroupListener.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management;
+
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import org.apache.ignite.raft.client.Command;
+import org.apache.ignite.raft.client.ReadCommand;
+import org.apache.ignite.raft.client.WriteCommand;
+import org.apache.ignite.raft.client.service.CommandClosure;
+import org.apache.ignite.raft.client.service.RaftGroupListener;
+import org.jetbrains.annotations.Nullable;
+
+// TODO: implement listener, https://issues.apache.org/jira/browse/IGNITE-16471
+class CmgRaftGroupListener implements RaftGroupListener {
+    @Override
+    public void onRead(Iterator<CommandClosure<ReadCommand>> iterator) {
+
+    }
+
+    @Override
+    public void onWrite(Iterator<CommandClosure<WriteCommand>> iterator) {
+
+    }
+
+    @Override
+    public void onSnapshotSave(Path path, Consumer<Throwable> doneClo) {
+
+    }
+
+    @Override
+    public boolean onSnapshotLoad(Path path) {
+        return false;
+    }
+
+    @Override
+    public void onShutdown() {
+
+    }
+
+    @Override
+    public @Nullable CompletableFuture<Void> onBeforeApply(Command command) {
+        return null;
+    }
+}
diff --git a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/NodeConfigurationSchema.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/InitException.java
similarity index 53%
rename from modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/NodeConfigurationSchema.java
rename to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/InitException.java
index dad6f45..acec838 100644
--- a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/NodeConfigurationSchema.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/InitException.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
@@ -15,18 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.configuration.schemas.runner;
+package org.apache.ignite.internal.cluster.management;
 
-import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.lang.IgniteInternalException;
 
 /**
- * Local node configuration schema.
+ * Exception thrown when cluster initialization fails for some reason.
  */
-@ConfigurationRoot(rootName = "node", type = ConfigurationType.LOCAL)
-public class NodeConfigurationSchema {
-    /** It is a copy of appropriate property from the cluster configuration. */
-    @Value(hasDefault = true)
-    public final String[] metastorageNodes = new String[0];
+public class InitException extends IgniteInternalException {
+    public InitException(String message) {
+        super(message);
+    }
+
+    public InitException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
diff --git a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/ClusterConfigurationSchema.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/CancelInitMessage.java
similarity index 51%
rename from modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/ClusterConfigurationSchema.java
rename to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/CancelInitMessage.java
index b5fdbc3..79d4824 100644
--- a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/ClusterConfigurationSchema.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/CancelInitMessage.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.configuration.schemas.runner;
+package org.apache.ignite.internal.cluster.management.messages;
 
-import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.annotations.Transferable;
 
 /**
- * Configuration schema for cluster endpoint subtree.
+ * Message signaling that the init process has failed and needs to be aborted.
  */
-@ConfigurationRoot(rootName = "cluster", type = ConfigurationType.DISTRIBUTED)
-public class ClusterConfigurationSchema {
-    /** List of unique names of those cluster nodes that will host distributed metastorage instances. */
-    @Value(hasDefault = true)
-    public String[] metastorageNodes = new String[0];
+@Transferable(CmgMessageGroup.CANCEL_INIT)
+public interface CancelInitMessage extends NetworkMessage {
+    /**
+     * Textual representation of the cause of init failure.
+     */
+    String reason();
 }
diff --git a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/ClusterStateMessage.java
similarity index 57%
copy from modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java
copy to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/ClusterStateMessage.java
index 2fdef3d..376e91d 100644
--- a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/ClusterStateMessage.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
@@ -15,8 +15,19 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.internal.cluster.management.messages;
+
+import java.util.Collection;
+import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.annotations.Transferable;
+
 /**
- * Configuration schemas for Cluster node.
+ * Message for initializing the Meta Storage.
  */
-
-package org.apache.ignite.configuration.schemas.runner;
+@Transferable(CmgMessageGroup.CLUSTER_STATE)
+public interface ClusterStateMessage extends NetworkMessage {
+    /**
+     * Consistent IDs of nodes that host the Meta Storage.
+     */
+    Collection<String> metastorageNodes();
+}
diff --git a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/CmgInitMessage.java
similarity index 56%
copy from modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java
copy to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/CmgInitMessage.java
index 2fdef3d..14356b6 100644
--- a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/CmgInitMessage.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
@@ -15,8 +15,21 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.internal.cluster.management.messages;
+
+import java.util.Collection;
+import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.annotations.Transferable;
+
 /**
- * Configuration schemas for Cluster node.
+ * Message for initializing the Cluster Management Group.
  */
+@Transferable(CmgMessageGroup.CMG_INIT)
+public interface CmgInitMessage extends NetworkMessage {
+    /**
+     * Consistent IDs of nodes that host the CMG.
+     */
+    Collection<String> cmgNodes();
 
-package org.apache.ignite.configuration.schemas.runner;
+    Collection<String> metaStorageNodes();
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/CmgMessageGroup.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/CmgMessageGroup.java
new file mode 100644
index 0000000..b90423e
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/CmgMessageGroup.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management.messages;
+
+import org.apache.ignite.network.annotations.MessageGroup;
+
+/**
+ * Message Group for cluster initialization and CMG management.
+ */
+@MessageGroup(groupType = 6, groupName = "CmgMessages")
+public class CmgMessageGroup {
+    /**
+     * Message type for {@link CmgInitMessage}.
+     */
+    public static final short CMG_INIT = 1;
+
+    /**
+     * Message type for {@link ClusterStateMessage}.
+     */
+    public static final short CLUSTER_STATE = 2;
+
+    /**
+     * Message type for {@link InitCompleteMessage}.
+     */
+    public static final short INIT_COMPLETE = 3;
+
+    /**
+     * Message type for {@link InitErrorMessage}.
+     */
+    public static final short INIT_ERROR = 4;
+
+    /**
+     * Message type for {@link CancelInitMessage}.
+     */
+    public static final short CANCEL_INIT = 5;
+}
diff --git a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/InitCompleteMessage.java
similarity index 64%
copy from modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java
copy to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/InitCompleteMessage.java
index 2fdef3d..d188cea 100644
--- a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/InitCompleteMessage.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
@@ -15,8 +15,14 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.internal.cluster.management.messages;
+
+import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.annotations.Transferable;
+
 /**
- * Configuration schemas for Cluster node.
+ * Successful response for initializing a Raft group.
  */
-
-package org.apache.ignite.configuration.schemas.runner;
+@Transferable(CmgMessageGroup.INIT_COMPLETE)
+public interface InitCompleteMessage extends NetworkMessage {
+}
diff --git a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/InitErrorMessage.java
similarity index 58%
rename from modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java
rename to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/InitErrorMessage.java
index 2fdef3d..169338c 100644
--- a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/package-info.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/messages/InitErrorMessage.java
@@ -1,6 +1,6 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
@@ -15,8 +15,18 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.internal.cluster.management.messages;
+
+import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.annotations.Transferable;
+
 /**
- * Configuration schemas for Cluster node.
+ * Message that represents an error condition that has occurred during cluster initialization.
  */
-
-package org.apache.ignite.configuration.schemas.runner;
+@Transferable(CmgMessageGroup.INIT_ERROR)
+public interface InitErrorMessage extends NetworkMessage {
+    /**
+     * Text representation of the occurred error.
+     */
+    String cause();
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommand.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommand.java
new file mode 100644
index 0000000..c841096
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommand.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management.rest;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * REST command for initializing a cluster.
+ */
+public class InitCommand {
+    private final Collection<String> metaStorageNodes;
+
+    private final Collection<String> cmgNodes;
+
+    @JsonCreator
+    public InitCommand(
+            @JsonProperty("metaStorageNodes") Collection<String> metaStorageNodes,
+            @JsonProperty("cmgNodes") Collection<String> cmgNodes
+    ) {
+        this.metaStorageNodes = List.copyOf(metaStorageNodes);
+        this.cmgNodes = List.copyOf(cmgNodes);
+    }
+
+    @JsonProperty
+    public Collection<String> metaStorageNodes() {
+        return metaStorageNodes;
+    }
+
+    @JsonProperty
+    public Collection<String> cmgNodes() {
+        return cmgNodes;
+    }
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommandHandler.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommandHandler.java
new file mode 100644
index 0000000..2150e27
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommandHandler.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management.rest;
+
+import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import org.apache.ignite.internal.cluster.management.ClusterInitializer;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.internal.rest.api.RequestHandler;
+import org.apache.ignite.internal.rest.api.RestApiHttpRequest;
+import org.apache.ignite.internal.rest.api.RestApiHttpResponse;
+import org.apache.ignite.lang.IgniteLogger;
+
+/**
+ * REST handler for the {@link InitCommand}.
+ */
+public class InitCommandHandler implements RequestHandler {
+    private static final IgniteLogger log = IgniteLogger.forClass(InitCommandHandler.class);
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    private final ClusterInitializer clusterInitializer;
+
+    public InitCommandHandler(ClusterInitializer clusterInitializer) {
+        this.clusterInitializer = clusterInitializer;
+    }
+
+    @Override
+    public CompletableFuture<RestApiHttpResponse> handle(RestApiHttpRequest request, RestApiHttpResponse response) {
+        try {
+            InitCommand command = readContent(request);
+
+            if (log.isInfoEnabled()) {
+                log.info(
+                        "Received init command:\n\tMeta Storage nodes: {}\n\tCMG nodes: {}",
+                        command.metaStorageNodes(),
+                        command.cmgNodes()
+                );
+            }
+
+            return clusterInitializer.initCluster(command.metaStorageNodes(), command.cmgNodes())
+                    .thenApply(v -> successResponse(response))
+                    .exceptionally(e -> errorResponse(response, e));
+        } catch (Exception e) {
+            return CompletableFuture.completedFuture(errorResponse(response, e));
+        }
+    }
+
+    private InitCommand readContent(RestApiHttpRequest restApiHttpRequest) throws IOException {
+        ByteBuf content = restApiHttpRequest.request().content();
+
+        try (InputStream is = new ByteBufInputStream(content)) {
+            return objectMapper.readValue(is, InitCommand.class);
+        }
+    }
+
+    private static RestApiHttpResponse successResponse(RestApiHttpResponse response) {
+        log.info("Init command executed successfully");
+
+        return response;
+    }
+
+    private static RestApiHttpResponse errorResponse(RestApiHttpResponse response, Throwable e) {
+        if (e instanceof CompletionException) {
+            e = e.getCause();
+        }
+
+        log.error("Init command failure", e);
+
+        response.status(INTERNAL_SERVER_ERROR);
+        response.json(Map.of("error", new ErrorResult("APPLICATION_EXCEPTION", e.getMessage())));
+
+        return response;
+    }
+}
diff --git a/modules/cluster-management/src/test/java/org/apache/ignite/internal/cluster/management/ClusterInitializerTest.java b/modules/cluster-management/src/test/java/org/apache/ignite/internal/cluster/management/ClusterInitializerTest.java
new file mode 100644
index 0000000..c3319a9
--- /dev/null
+++ b/modules/cluster-management/src/test/java/org/apache/ignite/internal/cluster/management/ClusterInitializerTest.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management;
+
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.isA;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.internal.cluster.management.messages.CancelInitMessage;
+import org.apache.ignite.internal.cluster.management.messages.CmgInitMessage;
+import org.apache.ignite.internal.cluster.management.messages.CmgMessagesFactory;
+import org.apache.ignite.network.ClusterNode;
+import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessagingService;
+import org.apache.ignite.network.NetworkAddress;
+import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.TopologyService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+/**
+ * Tests for {@link ClusterInitializer}.
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class ClusterInitializerTest {
+    @Mock
+    private MessagingService messagingService;
+
+    @Mock
+    private TopologyService topologyService;
+
+    private ClusterInitializer clusterInitializer;
+
+    private final CmgMessagesFactory msgFactory = new CmgMessagesFactory();
+
+    @BeforeEach
+    void setUp(@Mock ClusterService clusterService) {
+        when(clusterService.messagingService()).thenReturn(messagingService);
+        when(clusterService.topologyService()).thenReturn(topologyService);
+
+        clusterInitializer = new ClusterInitializer(clusterService);
+    }
+
+    /**
+     * Tests the happy-case scenario of cluster initialization.
+     */
+    @Test
+    void testNormalInit() {
+        ClusterNode metastorageNode = new ClusterNode("metastore", "metastore", new NetworkAddress("foo", 123));
+        ClusterNode cmgNode = new ClusterNode("cmg", "cmg", new NetworkAddress("bar", 456));
+
+        when(topologyService.getByConsistentId(metastorageNode.name())).thenReturn(metastorageNode);
+        when(topologyService.getByConsistentId(cmgNode.name())).thenReturn(cmgNode);
+        when(topologyService.allMembers()).thenReturn(List.of(metastorageNode, cmgNode));
+
+        when(messagingService.invoke(any(ClusterNode.class), any(CmgInitMessage.class), anyLong()))
+                .thenReturn(initCompleteMessage());
+
+        // check that leaders are different in case different node IDs are provided
+        CompletableFuture<Void> initFuture = clusterInitializer.initCluster(List.of(metastorageNode.name()), List.of(cmgNode.name()));
+
+        verify(messagingService).invoke(eq(cmgNode), any(CmgInitMessage.class), anyLong());
+        verify(messagingService, never()).invoke(eq(metastorageNode), any(CmgInitMessage.class), anyLong());
+
+        assertThat(initFuture, willBe(nullValue(Void.class)));
+    }
+
+    /**
+     * Tests the happy-case scenario of cluster initialization when only Meta Storage are provided.
+     */
+    @Test
+    void testNormalInitSingleNodeList() {
+        ClusterNode metastorageNode = new ClusterNode("metastore", "metastore", new NetworkAddress("foo", 123));
+        ClusterNode cmgNode = new ClusterNode("cmg", "cmg", new NetworkAddress("bar", 456));
+
+        when(topologyService.getByConsistentId(metastorageNode.name())).thenReturn(metastorageNode);
+        when(topologyService.getByConsistentId(cmgNode.name())).thenReturn(cmgNode);
+        when(topologyService.allMembers()).thenReturn(List.of(metastorageNode, cmgNode));
+
+        when(messagingService.invoke(any(ClusterNode.class), any(CmgInitMessage.class), anyLong()))
+                .thenReturn(initCompleteMessage());
+
+        CompletableFuture<Void> initFuture = clusterInitializer.initCluster(List.of(metastorageNode.name()), List.of());
+
+        verify(messagingService).invoke(eq(metastorageNode), any(CmgInitMessage.class), anyLong());
+        verify(messagingService, never()).invoke(eq(cmgNode), any(CmgInitMessage.class), anyLong());
+
+        assertThat(initFuture, willBe(nullValue(Void.class)));
+    }
+
+    /**
+     * Tests a situation when one of the nodes fail during initialization.
+     */
+    @Test
+    void testInitCancel() {
+        ClusterNode metastorageNode = new ClusterNode("metastore", "metastore", new NetworkAddress("foo", 123));
+        ClusterNode cmgNode = new ClusterNode("cmg", "cmg", new NetworkAddress("bar", 456));
+
+        when(topologyService.getByConsistentId(metastorageNode.name())).thenReturn(metastorageNode);
+        when(topologyService.getByConsistentId(cmgNode.name())).thenReturn(cmgNode);
+        when(topologyService.allMembers()).thenReturn(List.of(metastorageNode, cmgNode));
+
+        when(messagingService.invoke(eq(cmgNode), any(CmgInitMessage.class), anyLong()))
+                .thenAnswer(invocation -> {
+                    NetworkMessage response = msgFactory.initErrorMessage().cause("foobar").build();
+
+                    return CompletableFuture.completedFuture(response);
+                });
+
+        when(messagingService.send(any(ClusterNode.class), any(CancelInitMessage.class)))
+                .thenReturn(CompletableFuture.completedFuture(null));
+
+        CompletableFuture<Void> initFuture = clusterInitializer.initCluster(List.of(metastorageNode.name()), List.of(cmgNode.name()));
+
+        InitException e = assertFutureThrows(InitException.class, initFuture);
+
+        assertThat(e.getMessage(), containsString(String.format("Got error response from node \"%s\": foobar", cmgNode.name())));
+
+        verify(messagingService).send(eq(cmgNode), any(CancelInitMessage.class));
+        verify(messagingService, never()).send(eq(metastorageNode), any(CancelInitMessage.class));
+    }
+
+    private CompletableFuture<NetworkMessage> initCompleteMessage() {
+        NetworkMessage msg = msgFactory.initCompleteMessage().build();
+
+        return CompletableFuture.completedFuture(msg);
+    }
+
+    /**
+     * Tests that providing no nodes for the initialization throws an error.
+     */
+    @Test
+    void testEmptyInit() {
+        CompletableFuture<Void> initFuture = clusterInitializer.initCluster(List.of(), List.of());
+
+        assertFutureThrows(IllegalArgumentException.class, initFuture);
+    }
+
+    /**
+     * Tests that if some nodes are not present in the topology, an error is thrown.
+     */
+    @Test
+    void testUnresolvableNode() {
+        CompletableFuture<Void> initFuture = clusterInitializer.initCluster(List.of("foo"), List.of("bar"));
+
+        IllegalArgumentException e = assertFutureThrows(IllegalArgumentException.class, initFuture);
+
+        assertThat(e.getMessage(), containsString("Node \"foo\" is not present in the physical topology"));
+    }
+
+    private static <T extends Throwable> T assertFutureThrows(Class<T> expected, CompletableFuture<?> future) {
+        ExecutionException e = assertThrows(ExecutionException.class, () -> future.get(1, TimeUnit.SECONDS));
+
+        assertThat(e.getCause(), isA(expected));
+
+        return expected.cast(e.getCause());
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/testframework/BaseIgniteAbstractTest.java b/modules/core/src/test/java/org/apache/ignite/internal/testframework/BaseIgniteAbstractTest.java
index ddb2fed..66991ee 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/testframework/BaseIgniteAbstractTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/testframework/BaseIgniteAbstractTest.java
@@ -37,7 +37,7 @@ public abstract class BaseIgniteAbstractTest {
     /** Logger. */
     protected static IgniteLogger log;
 
-    /** Tets start milliseconds. */
+    /** Test start time in milliseconds. */
     private long testStartMs;
 
     /* Init test env. */
@@ -49,14 +49,13 @@ public abstract class BaseIgniteAbstractTest {
     /**
      * Should be invoked before a test will start.
      *
-     * @param testInfo Test information oject.
+     * @param testInfo Test information object.
      * @param workDir  Work directory.
-     * @throws Exception If failed.
      */
-    protected void setupBase(TestInfo testInfo, Path workDir) throws Exception {
+    protected void setupBase(TestInfo testInfo, Path workDir) {
         log.info(">>> Starting test: {}#{}, displayName: {}, workDir: {}",
-                testInfo.getTestClass().map(Class::getSimpleName).orElseGet(() -> "<null>"),
-                testInfo.getTestMethod().map(Method::getName).orElseGet(() -> "<null>"),
+                testInfo.getTestClass().map(Class::getSimpleName).orElse("<null>"),
+                testInfo.getTestMethod().map(Method::getName).orElse("<null>"),
                 testInfo.getDisplayName(),
                 workDir.toAbsolutePath());
 
@@ -66,13 +65,12 @@ public abstract class BaseIgniteAbstractTest {
     /**
      * Should be invoked after the test has finished.
      *
-     * @param testInfo Test information oject.
-     * @throws Exception If failed.
+     * @param testInfo Test information object.
      */
-    protected void tearDownBase(TestInfo testInfo) throws Exception {
+    protected void tearDownBase(TestInfo testInfo) {
         log.info(">>> Stopping test: {}#{}, displayName: {}, cost: {}ms.",
-                testInfo.getTestClass().map(Class::getSimpleName).orElseGet(() -> "<null>"),
-                testInfo.getTestMethod().map(Method::getName).orElseGet(() -> "<null>"),
+                testInfo.getTestClass().map(Class::getSimpleName).orElse("<null>"),
+                testInfo.getTestMethod().map(Method::getName).orElse("<null>"),
                 testInfo.getDisplayName(), monotonicMs() - testStartMs);
     }
 
diff --git a/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageService.java b/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageService.java
index 5b9af5c..f211a75 100644
--- a/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageService.java
+++ b/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageService.java
@@ -349,4 +349,3 @@ public interface MetaStorageService {
     @NotNull
     CompletableFuture<Void> closeCursors(@NotNull String nodeId);
 }
-
diff --git a/modules/metastorage/pom.xml b/modules/metastorage/pom.xml
index aa3b64e..bfa1a35 100644
--- a/modules/metastorage/pom.xml
+++ b/modules/metastorage/pom.xml
@@ -40,6 +40,11 @@
 
         <dependency>
             <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-cluster-management</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
             <artifactId>ignite-network-api</artifactId>
         </dependency>
 
diff --git a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
index e027665..7e18783 100644
--- a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
+++ b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
@@ -19,22 +19,18 @@ package org.apache.ignite.internal.metastorage;
 
 import static org.apache.ignite.internal.util.ByteUtils.bytesToLong;
 import static org.apache.ignite.internal.util.ByteUtils.longToBytes;
+import static org.apache.ignite.network.util.ClusterServiceUtils.resolveNodes;
 
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import org.apache.ignite.configuration.schemas.runner.NodeConfiguration;
-import org.apache.ignite.internal.configuration.ConfigurationManager;
+import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.client.CompactedException;
 import org.apache.ignite.internal.metastorage.client.Condition;
@@ -58,9 +54,7 @@ import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.vault.VaultManager;
 import org.apache.ignite.lang.ByteArray;
 import org.apache.ignite.lang.IgniteBiTuple;
-import org.apache.ignite.lang.IgniteException;
 import org.apache.ignite.lang.IgniteInternalException;
-import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.lang.NodeStoppingException;
 import org.apache.ignite.network.ClusterNode;
@@ -80,12 +74,7 @@ import org.jetbrains.annotations.Nullable;
  *     <li>Providing corresponding meta storage service proxy interface</li>
  * </ul>
  */
-// TODO: IGNITE-14586 Remove @SuppressWarnings when implementation provided.
-@SuppressWarnings("unused")
 public class MetaStorageManager implements IgniteComponent {
-    /** Logger. */
-    private static final IgniteLogger LOG = IgniteLogger.forClass(MetaStorageManager.class);
-
     /** Meta storage raft group name. */
     private static final String METASTORAGE_RAFT_GROUP_NAME = "metastorage_raft_group";
 
@@ -95,23 +84,16 @@ public class MetaStorageManager implements IgniteComponent {
      */
     public static final ByteArray APPLIED_REV = ByteArray.fromString("applied_revision");
 
+    private final ClusterService clusterService;
+
     /** Vault manager in order to commit processed watches with corresponding applied revision. */
     private final VaultManager vaultMgr;
 
-    /** Configuration manager that handles local configuration. */
-    private final ConfigurationManager locCfgMgr;
-
-    /** Cluster network service that is used in order to handle cluster init message. */
-    private final ClusterService clusterNetSvc;
-
     /** Raft manager that is used for metastorage raft group handling. */
     private final Loza raftMgr;
 
     /** Meta storage service. */
-    private volatile CompletableFuture<MetaStorageService> metaStorageSvcFut;
-
-    /** Raft group service. */
-    private volatile CompletableFuture<RaftGroupService> raftGroupServiceFut;
+    private final CompletableFuture<MetaStorageService> metaStorageSvcFut;
 
     /**
      * Aggregator of multiple watches to deploy them as one batch.
@@ -122,19 +104,11 @@ public class MetaStorageManager implements IgniteComponent {
 
     /**
      * Future which will be completed with {@link IgniteUuid}, when aggregated watch will be successfully deployed. Can be resolved to
-     * {@link Optional#empty()} if no watch deployed at the moment.
-     */
-    private CompletableFuture<Optional<IgniteUuid>> deployFut = new CompletableFuture<>();
-
-    /**
-     * If true - all new watches will be deployed immediately.
+     * {@code null} if no watch deployed at the moment.
      *
-     * <p>If false - all new watches will be aggregated to one batch for further deploy by {@link MetaStorageManager#deployWatches()}
+     * <p>Multi-threaded access is guarded by {@code this}.
      */
-    private boolean deployed;
-
-    /** Flag indicates if meta storage nodes were set on start. */
-    private boolean metaStorageNodesOnStart;
+    private CompletableFuture<IgniteUuid> deployFut = new CompletableFuture<>();
 
     /** Actual storage for the Metastorage. */
     private final KeyValueStorage storage;
@@ -142,154 +116,134 @@ public class MetaStorageManager implements IgniteComponent {
     /** Busy lock to stop synchronously. */
     private final IgniteSpinBusyLock busyLock = new IgniteSpinBusyLock();
 
+    /**
+     * If true - all new watches will be deployed immediately.
+     *
+     * <p>If false - all new watches will be aggregated to one batch for further deploy by {@link MetaStorageManager#deployWatches()}.
+     *
+     * <p>Multi-threaded access is guarded by {@code this}.
+     */
+    private boolean areWatchesDeployed = false;
+
+    /**
+     * Flag that indicates that the component has been initialized.
+     */
+    private volatile boolean isInitialized = false;
+
     /** Prevents double stopping the component. */
-    private final AtomicBoolean stopGuard = new AtomicBoolean();
+    private final AtomicBoolean isStopped = new AtomicBoolean();
 
     /**
      * The constructor.
      *
-     * @param vaultMgr      Vault manager.
-     * @param locCfgMgr     Local configuration manager.
-     * @param clusterNetSvc Cluster network service.
-     * @param raftMgr       Raft manager.
-     * @param storage       Storage. This component owns this resource and will manage its lifecycle.
+     * @param vaultMgr Vault manager.
+     * @param clusterService Cluster network service.
+     * @param raftMgr Raft manager.
+     * @param storage Storage. This component owns this resource and will manage its lifecycle.
      */
     public MetaStorageManager(
             VaultManager vaultMgr,
-            ConfigurationManager locCfgMgr,
-            ClusterService clusterNetSvc,
+            ClusterService clusterService,
+            ClusterManagementGroupManager cmgManager,
             Loza raftMgr,
             KeyValueStorage storage
     ) {
         this.vaultMgr = vaultMgr;
-        this.locCfgMgr = locCfgMgr;
-        this.clusterNetSvc = clusterNetSvc;
+        this.clusterService = clusterService;
         this.raftMgr = raftMgr;
         this.storage = storage;
-    }
 
-    /** {@inheritDoc} */
-    @Override
-    public void start() {
-        String[] metastorageNodes = this.locCfgMgr.configurationRegistry().getConfiguration(NodeConfiguration.KEY)
-                .metastorageNodes().value();
+        this.metaStorageSvcFut = cmgManager.metaStorageNodes()
+                // use default executor to avoid blocking CMG manager threads
+                .thenComposeAsync(metaStorageNodes -> {
+                    if (!busyLock.enterBusy()) {
+                        return CompletableFuture.failedFuture(new NodeStoppingException());
+                    }
 
-        Predicate<ClusterNode> metaStorageNodesContainsLocPred =
-                clusterNode -> Arrays.asList(metastorageNodes).contains(clusterNode.name());
+                    try {
+                        isInitialized = true;
 
-        if (metastorageNodes.length > 0) {
-            metaStorageNodesOnStart = true;
+                        return initializeMetaStorage(metaStorageNodes);
+                    } finally {
+                        busyLock.leaveBusy();
+                    }
+                });
+    }
 
-            List<ClusterNode> metaStorageMembers = clusterNetSvc.topologyService().allMembers().stream()
-                    .filter(metaStorageNodesContainsLocPred)
-                    .collect(Collectors.toList());
+    private CompletableFuture<MetaStorageService> initializeMetaStorage(Collection<String> metaStorageNodes) {
+        List<ClusterNode> metastorageNodes = resolveNodes(clusterService, metaStorageNodes);
 
-            // TODO: This is temporary solution for providing human-readable error when you try to start single-node cluster
-            // without hosting metastorage, this will be rewritten in init phase https://issues.apache.org/jira/browse/IGNITE-15114
-            if (metaStorageMembers.isEmpty()) {
-                throw new IgniteException(
-                        "Cannot start meta storage manager because there is no node in the cluster that hosts meta storage.");
-            }
+        ClusterNode thisNode = clusterService.topologyService().localMember();
 
-            // TODO: This is temporary solution. We need to prohibit starting several metastorage nodes
-            // as far as we do not have mechanism of changing raft peers when new metastorage node is joining to cluster.
-            // This will be rewritten in init phase https://issues.apache.org/jira/browse/IGNITE-15114
-            if (metastorageNodes.length > 1) {
-                throw new IgniteException(
-                        "Cannot start meta storage manager because it is not allowed to start several metastorage nodes.");
-            }
+        if (metastorageNodes.contains(thisNode)) {
+            clusterService.topologyService().addEventHandler(new TopologyEventHandler() {
+                @Override
+                public void onAppeared(ClusterNode member) {
+                    // No-op.
+                }
 
-            storage.start();
+                @Override
+                public void onDisappeared(ClusterNode member) {
+                    metaStorageSvcFut.thenAccept(svc -> svc.closeCursors(member.id()));
+                }
+            });
+        }
 
-            try {
-                raftGroupServiceFut = raftMgr.prepareRaftGroup(
-                        METASTORAGE_RAFT_GROUP_NAME,
-                        metaStorageMembers,
-                        () -> new MetaStorageListener(storage)
-                );
-            } catch (NodeStoppingException e) {
-                throw new AssertionError("Loza was stopped before Meta Storage manager", e);
-            }
+        storage.start();
 
-            this.metaStorageSvcFut = raftGroupServiceFut.thenApply(service ->
-                    new MetaStorageServiceImpl(service, clusterNetSvc.topologyService().localMember().id())
+        try {
+            CompletableFuture<RaftGroupService> raftServiceFuture = raftMgr.prepareRaftGroup(
+                    METASTORAGE_RAFT_GROUP_NAME,
+                    metastorageNodes,
+                    () -> new MetaStorageListener(storage)
             );
 
-            if (hasMetastorageLocally(locCfgMgr)) {
-                clusterNetSvc.topologyService().addEventHandler(new TopologyEventHandler() {
-                    @Override
-                    public void onAppeared(ClusterNode member) {
-                        // No-op.
-                    }
-
-                    @Override
-                    public void onDisappeared(ClusterNode member) {
-                        metaStorageSvcFut.thenCompose(svc -> svc.closeCursors(member.id()));
-                    }
-                });
-            }
-        } else {
-            this.metaStorageSvcFut = new CompletableFuture<>();
+            return raftServiceFuture.thenApply(service -> new MetaStorageServiceImpl(service, thisNode.id()));
+        } catch (NodeStoppingException e) {
+            return CompletableFuture.failedFuture(e);
         }
+    }
 
-        // TODO: IGNITE-15114 Cluster initialization flow. Here we should complete metaStorageServiceFuture.
-        //        clusterNetSvc.messagingService().addMessageHandler((message, senderAddr, correlationId) -> {});
+    /** {@inheritDoc} */
+    @Override
+    public void start() {
+        // NO-OP
     }
 
     /** {@inheritDoc} */
     @Override
-    public void stop() {
-        if (!stopGuard.compareAndSet(false, true)) {
+    public void stop() throws Exception {
+        if (!isStopped.compareAndSet(false, true)) {
             return;
         }
 
         busyLock.block();
 
-        Optional<IgniteUuid> watchId;
-
-        try {
-            // If deployed future is not done, that means that stop was called in the middle of
-            // IgniteImpl.start, before deployWatches, or before init phase.
-            // It is correct to check completeness of the future because the method calls are guarded by busy lock.
-            // TODO: add busy lock for init method https://issues.apache.org/jira/browse/IGNITE-15114
-            if (deployFut.isDone()) {
-                watchId = deployFut.get();
-
-                try {
-                    if (watchId.isPresent()) {
-                        metaStorageSvcFut.get().stopWatch(watchId.get());
-                    }
-                } catch (InterruptedException | ExecutionException e) {
-                    LOG.error("Failed to get meta storage service.");
-
-                    throw new IgniteInternalException(e);
-                }
-            }
-        } catch (InterruptedException | ExecutionException e) {
-            LOG.error("Failed to get watch.");
-
-            throw new IgniteInternalException(e);
+        if (!isInitialized) {
+            // Stop command was called before the init command was received
+            return;
         }
 
-        try {
-            if (raftGroupServiceFut != null) {
-                raftGroupServiceFut.get().shutdown();
-
-                raftMgr.stopRaftGroup(METASTORAGE_RAFT_GROUP_NAME);
-            }
-        } catch (InterruptedException | ExecutionException e) {
-            LOG.error("Failed to get meta storage raft group service.");
-
-            throw new IgniteInternalException(e);
-        } catch (NodeStoppingException e) {
-            throw new AssertionError("Loza was stopped before Meta Storage manager", e);
+        synchronized (this) {
+            IgniteUtils.closeAll(
+                    this::stopDeployedWatches,
+                    () -> raftMgr.stopRaftGroup(METASTORAGE_RAFT_GROUP_NAME),
+                    storage
+            );
         }
+    }
 
-        try {
-            storage.close();
-        } catch (Exception e) {
-            throw new IgniteInternalException("Exception when stopping the storage", e);
+    private void stopDeployedWatches() throws Exception {
+        if (!areWatchesDeployed) {
+            return;
         }
+
+        deployFut
+                .thenCompose(watchId -> watchId == null
+                        ? CompletableFuture.completedFuture(null)
+                        : metaStorageSvcFut.thenAccept(service -> service.stopWatch(watchId))
+                )
+                .get();
     }
 
     /**
@@ -301,25 +255,17 @@ public class MetaStorageManager implements IgniteComponent {
         }
 
         try {
-            var watch = watchAggregator.watch(
-                    appliedRevision() + 1,
-                    this::storeEntries
-            );
-
-            if (watch.isEmpty()) {
-                deployFut.complete(Optional.empty());
-            } else {
-                CompletableFuture<Void> fut =
-                        dispatchAppropriateMetaStorageWatch(watch.get()).thenAccept(id -> deployFut.complete(Optional.of(id)));
+            CompletableFuture<IgniteUuid> deployFut = this.deployFut;
 
-                if (metaStorageNodesOnStart) {
-                    fut.join();
+            updateAggregatedWatch().whenComplete((id, ex) -> {
+                if (ex == null) {
+                    deployFut.complete(id);
                 } else {
-                    // TODO: need to wait for this future in init phase https://issues.apache.org/jira/browse/IGNITE-15114
+                    deployFut.completeExceptionally(ex);
                 }
-            }
+            });
 
-            deployed = true;
+            areWatchesDeployed = true;
         } finally {
             busyLock.leaveBusy();
         }
@@ -328,20 +274,19 @@ public class MetaStorageManager implements IgniteComponent {
     /**
      * Register watch listener by key.
      *
-     * @param key  The target key.
+     * @param key The target key.
      * @param lsnr Listener which will be notified for each update.
      * @return Subscription identifier. Could be used in {@link #unregisterWatch} method in order to cancel subscription
      */
-    public synchronized CompletableFuture<Long> registerWatch(
-            @Nullable ByteArray key,
-            @NotNull WatchListener lsnr
-    ) {
+    public synchronized CompletableFuture<Long> registerWatch(ByteArray key, WatchListener lsnr) {
         if (!busyLock.enterBusy()) {
             return CompletableFuture.failedFuture(new NodeStoppingException());
         }
 
         try {
-            return waitForReDeploy(watchAggregator.add(key, lsnr));
+            long watchId = watchAggregator.add(key, lsnr);
+
+            return updateWatches().thenApply(uuid -> watchId);
         } finally {
             busyLock.leaveBusy();
         }
@@ -354,16 +299,15 @@ public class MetaStorageManager implements IgniteComponent {
      * @param lsnr Listener which will be notified for each update.
      * @return Subscription identifier. Could be used in {@link #unregisterWatch} method in order to cancel subscription
      */
-    public synchronized CompletableFuture<Long> registerWatch(
-            @NotNull Collection<ByteArray> keys,
-            @NotNull WatchListener lsnr
-    ) {
+    public synchronized CompletableFuture<Long> registerWatch(Collection<ByteArray> keys, WatchListener lsnr) {
         if (!busyLock.enterBusy()) {
             return CompletableFuture.failedFuture(new NodeStoppingException());
         }
 
         try {
-            return waitForReDeploy(watchAggregator.add(keys, lsnr));
+            long watchId = watchAggregator.add(keys, lsnr);
+
+            return updateWatches().thenApply(uuid -> watchId);
         } finally {
             busyLock.leaveBusy();
         }
@@ -373,7 +317,7 @@ public class MetaStorageManager implements IgniteComponent {
      * Register watch listener by range of keys.
      *
      * @param from Start key of range.
-     * @param to   End key of range (exclusively).
+     * @param to End key of range (exclusively).
      * @param lsnr Listener which will be notified for each update.
      * @return future with id of registered watch.
      */
@@ -387,7 +331,9 @@ public class MetaStorageManager implements IgniteComponent {
         }
 
         try {
-            return waitForReDeploy(watchAggregator.add(from, to, lsnr));
+            long watchId = watchAggregator.add(from, to, lsnr);
+
+            return updateWatches().thenApply(uuid -> watchId);
         } finally {
             busyLock.leaveBusy();
         }
@@ -396,20 +342,19 @@ public class MetaStorageManager implements IgniteComponent {
     /**
      * Register watch listener by key prefix.
      *
-     * @param key  Prefix to listen.
+     * @param key Prefix to listen.
      * @param lsnr Listener which will be notified for each update.
      * @return Subscription identifier. Could be used in {@link #unregisterWatch} method in order to cancel subscription
      */
-    public synchronized CompletableFuture<Long> registerWatchByPrefix(
-            @Nullable ByteArray key,
-            @NotNull WatchListener lsnr
-    ) {
+    public synchronized CompletableFuture<Long> registerWatchByPrefix(ByteArray key, WatchListener lsnr) {
         if (!busyLock.enterBusy()) {
             return CompletableFuture.failedFuture(new NodeStoppingException());
         }
 
         try {
-            return waitForReDeploy(watchAggregator.addPrefix(key, lsnr));
+            long watchId = watchAggregator.addPrefix(key, lsnr);
+
+            return updateWatches().thenApply(uuid -> watchId);
         } finally {
             busyLock.leaveBusy();
         }
@@ -428,13 +373,8 @@ public class MetaStorageManager implements IgniteComponent {
 
         try {
             watchAggregator.cancel(id);
-            if (deployed) {
-                return updateWatches().thenAccept(v -> {
-                });
-            } else {
-                return deployFut.thenAccept(uuid -> {
-                });
-            }
+
+            return updateWatches().thenApply(uuid -> null);
         } finally {
             busyLock.leaveBusy();
         }
@@ -689,7 +629,7 @@ public class MetaStorageManager implements IgniteComponent {
     /**
      * Invoke, which supports nested conditional statements.
      *
-     * @see MetaStorageService#invoke(org.apache.ignite.internal.metastorage.client.If)
+     * @see MetaStorageService#invoke(If)
      */
     public @NotNull CompletableFuture<StatementResult> invoke(@NotNull If iif) {
         if (!busyLock.enterBusy()) {
@@ -704,8 +644,8 @@ public class MetaStorageManager implements IgniteComponent {
     }
 
     /**
-     * Retrieves entries for the given key range in lexicographic order.
-     * Entries will be filtered out by upper bound of given revision number.
+     * Retrieves entries for the given key range in lexicographic order. Entries will be filtered out by upper bound of given revision
+     * number.
      *
      * @see MetaStorageService#range(ByteArray, ByteArray, long)
      */
@@ -716,9 +656,7 @@ public class MetaStorageManager implements IgniteComponent {
         }
 
         try {
-            return new CursorWrapper<>(
-                    metaStorageSvcFut.thenApply(svc -> svc.range(keyFrom, keyTo, revUpperBound))
-            );
+            return new CursorWrapper<>(metaStorageSvcFut.thenApply(svc -> svc.range(keyFrom, keyTo, revUpperBound)));
         } finally {
             busyLock.leaveBusy();
         }
@@ -735,9 +673,7 @@ public class MetaStorageManager implements IgniteComponent {
         }
 
         try {
-            return new CursorWrapper<>(
-                    metaStorageSvcFut.thenApply(svc -> svc.range(keyFrom, keyTo))
-            );
+            return new CursorWrapper<>(metaStorageSvcFut.thenApply(svc -> svc.range(keyFrom, keyTo)));
         } finally {
             busyLock.leaveBusy();
         }
@@ -748,10 +684,10 @@ public class MetaStorageManager implements IgniteComponent {
      * upper bound. Applied revision is a revision of the last successful vault update.
      *
      * @param keyFrom Start key of range (inclusive). Couldn't be {@code null}.
-     * @param keyTo   End key of range (exclusive). Could be {@code null}.
+     * @param keyTo End key of range (exclusive). Could be {@code null}.
      * @return Cursor built upon entries corresponding to the given range and applied revision.
      * @throws OperationTimeoutException If the operation is timed out.
-     * @throws CompactedException        If the desired revisions are removed from the storage due to a compaction.
+     * @throws CompactedException If the desired revisions are removed from the storage due to a compaction.
      * @see ByteArray
      * @see Entry
      */
@@ -762,9 +698,7 @@ public class MetaStorageManager implements IgniteComponent {
         }
 
         try {
-            return new CursorWrapper<>(
-                    metaStorageSvcFut.thenApply(svc -> svc.range(keyFrom, keyTo, appliedRevision()))
-            );
+            return new CursorWrapper<>(metaStorageSvcFut.thenApply(svc -> svc.range(keyFrom, keyTo, appliedRevision())));
         } finally {
             busyLock.leaveBusy();
         }
@@ -779,7 +713,7 @@ public class MetaStorageManager implements IgniteComponent {
      * @param keyPrefix Prefix of the key to retrieve the entries. Couldn't be {@code null}.
      * @return Cursor built upon entries corresponding to the given range and applied revision.
      * @throws OperationTimeoutException If the operation is timed out.
-     * @throws CompactedException        If the desired revisions are removed from the storage due to a compaction.
+     * @throws CompactedException If the desired revisions are removed from the storage due to a compaction.
      * @see ByteArray
      * @see Entry
      */
@@ -806,7 +740,7 @@ public class MetaStorageManager implements IgniteComponent {
      * @param keyPrefix Prefix of the key to retrieve the entries. Couldn't be {@code null}.
      * @return Cursor built upon entries corresponding to the given range and revision.
      * @throws OperationTimeoutException If the operation is timed out.
-     * @throws CompactedException        If the desired revisions are removed from the storage due to a compaction.
+     * @throws CompactedException If the desired revisions are removed from the storage due to a compaction.
      * @see ByteArray
      * @see Entry
      */
@@ -820,11 +754,11 @@ public class MetaStorageManager implements IgniteComponent {
      *
      * <p>Prefix query is a synonym of the range query {@code range(prefixKey, nextKey(prefixKey))}.
      *
-     * @param keyPrefix     Prefix of the key to retrieve the entries. Couldn't be {@code null}.
+     * @param keyPrefix Prefix of the key to retrieve the entries. Couldn't be {@code null}.
      * @param revUpperBound The upper bound for entry revision. {@code -1} means latest revision.
      * @return Cursor built upon entries corresponding to the given range and revision.
      * @throws OperationTimeoutException If the operation is timed out.
-     * @throws CompactedException        If the desired revisions are removed from the storage due to a compaction.
+     * @throws CompactedException If the desired revisions are removed from the storage due to a compaction.
      * @see ByteArray
      * @see Entry
      */
@@ -872,30 +806,35 @@ public class MetaStorageManager implements IgniteComponent {
     /**
      * Stop current batch of consolidated watches and register new one from current {@link WatchAggregator}.
      *
+     * <p>This method MUST always be called under a {@code synchronized} block.
+     *
      * @return Ignite UUID of new consolidated watch.
      */
-    private CompletableFuture<Optional<IgniteUuid>> updateWatches() {
-        long revision = appliedRevision() + 1;
+    private CompletableFuture<IgniteUuid> updateWatches() {
+        if (!areWatchesDeployed) {
+            return deployFut;
+        }
 
         deployFut = deployFut
-                .thenCompose(idOpt ->
-                        idOpt
-                                .map(id -> metaStorageSvcFut.thenCompose(svc -> svc.stopWatch(id)))
-                                .orElseGet(() -> CompletableFuture.completedFuture(null))
+                .thenCompose(id -> id == null
+                        ? CompletableFuture.completedFuture(null)
+                        : metaStorageSvcFut.thenCompose(svc -> svc.stopWatch(id))
                 )
-                .thenCompose(r ->
-                        watchAggregator.watch(revision, this::storeEntries)
-                                .map(watch -> dispatchAppropriateMetaStorageWatch(watch).thenApply(Optional::of))
-                                .orElseGet(() -> CompletableFuture.completedFuture(Optional.empty()))
-                );
+                .thenCompose(r -> updateAggregatedWatch());
 
         return deployFut;
     }
 
+    private CompletableFuture<IgniteUuid> updateAggregatedWatch() {
+        return watchAggregator.watch(appliedRevision() + 1, this::storeEntries)
+                .map(this::dispatchAppropriateMetaStorageWatch)
+                .orElseGet(() -> CompletableFuture.completedFuture(null));
+    }
+
     /**
      * Store entries with appropriate associated revision.
      *
-     * @param entries  to store.
+     * @param entries to store.
      * @param revision associated revision.
      */
     private void storeEntries(Collection<IgniteBiTuple<ByteArray, byte[]>> entries, long revision) {
@@ -919,56 +858,6 @@ public class MetaStorageManager implements IgniteComponent {
         vaultMgr.putAll(batch).join();
     }
 
-    /**
-     * Returns future, which will be completed after redeploy finished.
-     *
-     * @param id Id of watch to redeploy.
-     */
-    private CompletableFuture<Long> waitForReDeploy(long id) {
-        if (deployed) {
-            return updateWatches().thenApply(uid -> id);
-        } else {
-            return deployFut.thenApply(uid -> id);
-        }
-    }
-
-    /**
-     * Checks whether the given node hosts meta storage.
-     *
-     * @param nodeName           Node unique name.
-     * @param metastorageMembers Meta storage members names.
-     * @return {@code true} if the node has meta storage, {@code false} otherwise.
-     */
-    public static boolean hasMetastorage(String nodeName, String[] metastorageMembers) {
-        boolean isNodeHasMetasorage = false;
-
-        for (String name : metastorageMembers) {
-            if (name.equals(nodeName)) {
-                isNodeHasMetasorage = true;
-
-                break;
-            }
-        }
-
-        return isNodeHasMetasorage;
-    }
-
-    /**
-     * Checks whether the local node hosts meta storage.
-     *
-     * @param configurationMgr Configuration manager.
-     * @return {@code true} if the node has meta storage, {@code false} otherwise.
-     */
-    public boolean hasMetastorageLocally(ConfigurationManager configurationMgr) {
-        String[] metastorageMembers = configurationMgr
-                .configurationRegistry()
-                .getConfiguration(NodeConfiguration.KEY)
-                .metastorageNodes()
-                .value();
-
-        return hasMetastorage(vaultMgr.name().join(), metastorageMembers);
-    }
-
     // TODO: IGNITE-14691 Temporally solution that should be removed after implementing reactive watches.
 
     /** Cursor wrapper. */
@@ -1104,23 +993,4 @@ public class MetaStorageManager implements IgniteComponent {
             throw new UnsupportedOperationException("Unsupported type of criterion");
         }
     }
-
-    /**
-     * Return metastorage nodes.
-     *
-     * <p>This code will be deleted after node init phase is developed. https://issues.apache.org/jira/browse/IGNITE-15114
-     */
-    private List<ClusterNode> metastorageNodes() {
-        String[] metastorageNodes = this.locCfgMgr.configurationRegistry().getConfiguration(NodeConfiguration.KEY)
-                .metastorageNodes().value();
-
-        Predicate<ClusterNode> metaStorageNodesContainsLocPred =
-                clusterNode -> Arrays.asList(metastorageNodes).contains(clusterNode.name());
-
-        List<ClusterNode> metaStorageMembers = clusterNetSvc.topologyService().allMembers().stream()
-                .filter(metaStorageNodesContainsLocPred)
-                .collect(Collectors.toList());
-
-        return metaStorageMembers;
-    }
 }
diff --git a/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageDeserializerGenerator.java b/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageDeserializerGenerator.java
index 47a1ce8..5bee905 100644
--- a/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageDeserializerGenerator.java
+++ b/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageDeserializerGenerator.java
@@ -124,28 +124,30 @@ public class MessageDeserializerGenerator {
                 .endControlFlow()
                 .addCode("\n");
 
-        method.beginControlFlow("switch (reader.state())");
-
-        for (int i = 0; i < getters.size(); ++i) {
-            method
-                    .beginControlFlow("case $L:", i)
-                    .addStatement(readMessageCodeBlock(getters.get(i), msgField))
-                    .addCode("\n")
-                    .addCode(CodeBlock.builder()
-                            .beginControlFlow("if (!reader.isLastRead())")
-                            .addStatement("return false")
-                            .endControlFlow()
-                            .build()
-                    )
-                    .addCode("\n")
-                    .addStatement("reader.incrementState()")
-                    .endControlFlow()
-                    .addComment("Falls through");
+        if (!getters.isEmpty()) {
+            method.beginControlFlow("switch (reader.state())");
+
+            for (int i = 0; i < getters.size(); ++i) {
+                method
+                        .beginControlFlow("case $L:", i)
+                        .addStatement(readMessageCodeBlock(getters.get(i), msgField))
+                        .addCode("\n")
+                        .addCode(CodeBlock.builder()
+                                .beginControlFlow("if (!reader.isLastRead())")
+                                .addStatement("return false")
+                                .endControlFlow()
+                                .build()
+                        )
+                        .addCode("\n")
+                        .addStatement("reader.incrementState()")
+                        .endControlFlow()
+                        .addComment("Falls through");
+            }
+
+            method.endControlFlow().addCode("\n");
         }
 
-        method.endControlFlow();
-
-        method.addCode("\n").addStatement("return reader.afterMessageRead($T.class)", message.className());
+        method.addStatement("return reader.afterMessageRead($T.class)", message.className());
 
         return method.build();
     }
diff --git a/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageSerializerGenerator.java b/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageSerializerGenerator.java
index 7d579e4..002ddd3 100644
--- a/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageSerializerGenerator.java
+++ b/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageSerializerGenerator.java
@@ -100,21 +100,23 @@ public class MessageSerializerGenerator {
                 .endControlFlow()
                 .addCode("\n");
 
-        method.beginControlFlow("switch (writer.state())");
-
-        for (int i = 0; i < getters.size(); ++i) {
-            method
-                    .beginControlFlow("case $L:", i)
-                    .addCode(writeMessageCodeBlock(getters.get(i)))
-                    .addCode("\n")
-                    .addStatement("writer.incrementState()")
-                    .endControlFlow()
-                    .addComment("Falls through");
+        if (!getters.isEmpty()) {
+            method.beginControlFlow("switch (writer.state())");
+
+            for (int i = 0; i < getters.size(); ++i) {
+                method
+                        .beginControlFlow("case $L:", i)
+                        .addCode(writeMessageCodeBlock(getters.get(i)))
+                        .addCode("\n")
+                        .addStatement("writer.incrementState()")
+                        .endControlFlow()
+                        .addComment("Falls through");
+            }
+
+            method.endControlFlow().addCode("\n");
         }
 
-        method.endControlFlow();
-
-        method.addCode("\n").addStatement("return true");
+        method.addStatement("return true");
 
         return method.build();
     }
diff --git a/modules/network-api/src/main/java/org/apache/ignite/network/util/ClusterServiceUtils.java b/modules/network-api/src/main/java/org/apache/ignite/network/util/ClusterServiceUtils.java
new file mode 100644
index 0000000..da1500a
--- /dev/null
+++ b/modules/network-api/src/main/java/org/apache/ignite/network/util/ClusterServiceUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.network.util;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.ignite.network.ClusterNode;
+import org.apache.ignite.network.ClusterService;
+
+/**
+ * Various utilities related to {@link ClusterService}.
+ */
+public class ClusterServiceUtils {
+    private ClusterServiceUtils() {
+    }
+
+    /**
+     * Resolves given node names (a.k.a. consistent IDs) into {@link ClusterNode}s.
+     *
+     * @param consistentIds consistent IDs.
+     * @return list of resolved {@code ClusterNode}s.
+     * @throws IllegalArgumentException if any of the given nodes are not present in the physical topology.
+     */
+    public static List<ClusterNode> resolveNodes(ClusterService clusterService, Collection<String> consistentIds) {
+        return consistentIds.stream()
+                .map(consistentId -> {
+                    ClusterNode node = clusterService.topologyService().getByConsistentId(consistentId);
+
+                    if (node == null) {
+                        throw new IllegalArgumentException(String.format(
+                                "Node \"%s\" is not present in the physical topology", consistentId
+                        ));
+                    }
+
+                    return node;
+                })
+                .collect(Collectors.toList());
+    }
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java
index 16a1b9b..2dc53f3 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java
@@ -80,4 +80,17 @@ public interface Routes {
         addRoute(new Route(route, HttpMethod.PATCH, acceptType, hnd));
         return this;
     }
+
+    /**
+     * Adds a POST handler.
+     *
+     * @param route      Route.
+     * @param acceptType Accept type.
+     * @param hnd        Actual handler of the request.
+     * @return Router
+     */
+    default Routes post(String route, String acceptType, RequestHandler hnd) {
+        addRoute(new Route(route, HttpMethod.POST, acceptType, hnd));
+        return this;
+    }
 }
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
index 247061c..f660e03 100644
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
+++ b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
@@ -23,6 +23,7 @@ import io.netty.channel.ChannelFuture;
 import io.netty.handler.logging.LogLevel;
 import io.netty.handler.logging.LoggingHandler;
 import java.net.BindException;
+import java.net.InetSocketAddress;
 import java.util.function.Consumer;
 import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
 import org.apache.ignite.configuration.schemas.rest.RestView;
@@ -35,6 +36,7 @@ import org.apache.ignite.internal.rest.netty.RestApiInitializer;
 import org.apache.ignite.internal.rest.routes.Router;
 import org.apache.ignite.internal.rest.routes.SimpleRouter;
 import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.network.NettyBootstrapFactory;
 
@@ -146,4 +148,20 @@ public class RestComponent implements RestHandlersRegister, IgniteComponent {
             channel = null;
         }
     }
+
+    /**
+     * Returns the local address that the REST endpoint is bound to.
+     *
+     * @return local REST address.
+     * @throws IgniteInternalException if the component has not been started yet.
+     */
+    public InetSocketAddress localAddress() {
+        Channel channel = this.channel;
+
+        if (channel == null) {
+            throw new IgniteInternalException("RestComponent has not been started");
+        }
+
+        return (InetSocketAddress) channel.localAddress();
+    }
 }
diff --git a/modules/runner/pom.xml b/modules/runner/pom.xml
index 0c0d7db..bf32157 100644
--- a/modules/runner/pom.xml
+++ b/modules/runner/pom.xml
@@ -41,7 +41,6 @@
         <dependency>
             <groupId>org.apache.ignite</groupId>
             <artifactId>ignite-rest</artifactId>
-            <scope>compile</scope>
         </dependency>
 
         <dependency>
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
index de684e6..efb0f7c 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
@@ -17,10 +17,8 @@
 
 package org.apache.ignite.internal.configuration;
 
-import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toUnmodifiableList;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.directProxy;
-import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
 import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -28,6 +26,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
 
 import java.nio.file.Path;
 import java.util.List;
@@ -39,14 +38,14 @@ import org.apache.ignite.configuration.ConfigurationValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.schemas.runner.NodeConfiguration;
+import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorageListener;
 import org.apache.ignite.internal.configuration.storage.DistributedConfigurationStorage;
-import org.apache.ignite.internal.configuration.storage.LocalConfigurationStorage;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStorage;
 import org.apache.ignite.internal.raft.Loza;
+import org.apache.ignite.internal.rest.RestComponent;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.internal.util.IgniteUtils;
@@ -80,15 +79,13 @@ public class ItDistributedConfigurationPropertiesTest {
      * An emulation of an Ignite node, that only contains components necessary for tests.
      */
     private static class Node {
-        private final List<String> metaStorageNodes;
-
         private final VaultManager vaultManager;
 
         private final ClusterService clusterService;
 
         private final Loza raftManager;
 
-        private final ConfigurationManager cfgManager;
+        private final ClusterManagementGroupManager cmgManager;
 
         private final MetaStorageManager metaStorageManager;
 
@@ -104,11 +101,8 @@ public class ItDistributedConfigurationPropertiesTest {
                 TestInfo testInfo,
                 Path workDir,
                 NetworkAddress addr,
-                List<NetworkAddress> memberAddrs,
-                List<String> metaStorageNodes
+                List<NetworkAddress> memberAddrs
         ) {
-            this.metaStorageNodes = metaStorageNodes;
-
             vaultManager = new VaultManager(new InMemoryVaultService());
 
             clusterService = ClusterServiceTestUtils.clusterService(
@@ -120,18 +114,12 @@ public class ItDistributedConfigurationPropertiesTest {
 
             raftManager = new Loza(clusterService, workDir);
 
-            cfgManager = new ConfigurationManager(
-                    List.of(NodeConfiguration.KEY),
-                    Map.of(),
-                    new LocalConfigurationStorage(vaultManager),
-                    List.of(),
-                    List.of()
-            );
+            cmgManager = new ClusterManagementGroupManager(clusterService, raftManager, mock(RestComponent.class));
 
             metaStorageManager = new MetaStorageManager(
                     vaultManager,
-                    cfgManager,
                     clusterService,
+                    cmgManager,
                     raftManager,
                     new SimpleInMemoryKeyValueStorage()
             );
@@ -166,26 +154,13 @@ public class ItDistributedConfigurationPropertiesTest {
         void start() throws Exception {
             vaultManager.start();
 
-            cfgManager.start();
-
-            // metastorage configuration
-            String metaStorageCfg = metaStorageNodes.stream()
-                    .map(Object::toString)
-                    .collect(joining("\", \"", "\"", "\""));
-
-            var config = String.format("{ node: { metastorageNodes : [ %s ] } }", metaStorageCfg);
-
-            cfgManager.bootstrap(config);
-
-            Stream.of(clusterService, raftManager, metaStorageManager)
+            Stream.of(clusterService, raftManager, cmgManager, metaStorageManager)
                     .forEach(IgniteComponent::start);
 
             // deploy watches to propagate data from the metastore into the vault
             metaStorageManager.deployWatches();
 
             distributedCfgManager.start();
-
-            distributedCfgManager.configurationRegistry().initializeDefaults();
         }
 
         /**
@@ -193,7 +168,7 @@ public class ItDistributedConfigurationPropertiesTest {
          */
         void stop() throws Exception {
             var components = List.of(
-                    distributedCfgManager, metaStorageManager, raftManager, clusterService, cfgManager, vaultManager
+                    distributedCfgManager, cmgManager, metaStorageManager, raftManager, clusterService, vaultManager
             );
 
             for (IgniteComponent igniteComponent : components) {
@@ -211,6 +186,10 @@ public class ItDistributedConfigurationPropertiesTest {
         void stopReceivingUpdates() {
             receivesUpdates = false;
         }
+
+        String name() {
+            return clusterService.topologyService().localMember().name();
+        }
     }
 
     private Node firstNode;
@@ -224,28 +203,28 @@ public class ItDistributedConfigurationPropertiesTest {
     void setUp(@WorkDirectory Path workDir, TestInfo testInfo) throws Exception {
         var firstNodeAddr = new NetworkAddress("localhost", 10000);
 
-        String firstNodeName = testNodeName(testInfo, firstNodeAddr.port());
-
         var secondNodeAddr = new NetworkAddress("localhost", 10001);
 
+        List<NetworkAddress> allNodes = List.of(firstNodeAddr, secondNodeAddr);
+
         firstNode = new Node(
                 testInfo,
                 workDir.resolve("firstNode"),
                 firstNodeAddr,
-                List.of(firstNodeAddr, secondNodeAddr),
-                List.of(firstNodeName)
+                allNodes
         );
 
         secondNode = new Node(
                 testInfo,
                 workDir.resolve("secondNode"),
                 secondNodeAddr,
-                List.of(firstNodeAddr, secondNodeAddr),
-                List.of(firstNodeName)
+                allNodes
         );
 
         firstNode.start();
         secondNode.start();
+
+        firstNode.cmgManager.initCluster(List.of(firstNode.name()), List.of());
     }
 
     /**
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
index 58b4ea4..c6da615 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
@@ -19,11 +19,11 @@ package org.apache.ignite.internal.configuration.storage;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
 import static org.apache.ignite.internal.metastorage.MetaStorageManager.APPLIED_REV;
-import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
 import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
+import static org.mockito.Mockito.mock;
 
 import java.io.Serializable;
 import java.nio.file.Path;
@@ -31,19 +31,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Stream;
-import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.schemas.runner.NodeConfiguration;
-import org.apache.ignite.internal.configuration.ConfigurationManager;
+import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStorage;
 import org.apache.ignite.internal.raft.Loza;
-import org.apache.ignite.internal.table.distributed.TableTxManagerImpl;
+import org.apache.ignite.internal.rest.RestComponent;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
-import org.apache.ignite.internal.tx.LockManager;
-import org.apache.ignite.internal.tx.TxManager;
-import org.apache.ignite.internal.tx.impl.HeapLockManager;
 import org.apache.ignite.internal.vault.VaultManager;
 import org.apache.ignite.internal.vault.persistence.PersistentVaultService;
 import org.apache.ignite.network.ClusterService;
@@ -64,20 +59,14 @@ public class ItDistributedConfigurationStorageTest {
      * An emulation of an Ignite node, that only contains components necessary for tests.
      */
     private static class Node {
-        private final String name;
-
         private final VaultManager vaultManager;
 
         private final ClusterService clusterService;
 
-        private final LockManager lockManager;
-
-        private final TxManager txManager;
+        private final ClusterManagementGroupManager cmgManager;
 
         private final Loza raftManager;
 
-        private final ConfigurationManager cfgManager;
-
         private final MetaStorageManager metaStorageManager;
 
         private final DistributedConfigurationStorage cfgStorage;
@@ -88,8 +77,6 @@ public class ItDistributedConfigurationStorageTest {
         Node(TestInfo testInfo, Path workDir) {
             var addr = new NetworkAddress("localhost", 10000);
 
-            name = testNodeName(testInfo, addr.port());
-
             vaultManager = new VaultManager(new PersistentVaultService(workDir.resolve("vault")));
 
             clusterService = ClusterServiceTestUtils.clusterService(
@@ -99,26 +86,14 @@ public class ItDistributedConfigurationStorageTest {
                     new TestScaleCubeClusterServiceFactory()
             );
 
-            lockManager = new HeapLockManager();
-
             raftManager = new Loza(clusterService, workDir);
 
-            txManager = new TableTxManagerImpl(clusterService, lockManager);
-
-            List<RootKey<?, ?>> rootKeys = List.of(NodeConfiguration.KEY);
-
-            cfgManager = new ConfigurationManager(
-                    rootKeys,
-                    Map.of(),
-                    new LocalConfigurationStorage(vaultManager),
-                    List.of(),
-                    List.of()
-            );
+            cmgManager = new ClusterManagementGroupManager(clusterService, raftManager, mock(RestComponent.class));
 
             metaStorageManager = new MetaStorageManager(
                     vaultManager,
-                    cfgManager,
                     clusterService,
+                    cmgManager,
                     raftManager,
                     new SimpleInMemoryKeyValueStorage()
             );
@@ -132,14 +107,7 @@ public class ItDistributedConfigurationStorageTest {
         void start() throws Exception {
             vaultManager.start();
 
-            cfgManager.start();
-
-            // metastorage configuration
-            var config = String.format("{\"node\": {\"metastorageNodes\": [ \"%s\" ]}}", name);
-
-            cfgManager.bootstrap(config);
-
-            Stream.of(clusterService, raftManager, txManager, metaStorageManager).forEach(IgniteComponent::start);
+            Stream.of(clusterService, raftManager, cmgManager, metaStorageManager).forEach(IgniteComponent::start);
 
             // this is needed to avoid assertion errors
             cfgStorage.registerConfigurationListener(changedEntries -> completedFuture(null));
@@ -153,7 +121,7 @@ public class ItDistributedConfigurationStorageTest {
          */
         void stop() throws Exception {
             var components =
-                    List.of(metaStorageManager, raftManager, txManager, clusterService, cfgManager, vaultManager);
+                    List.of(metaStorageManager, cmgManager, raftManager, clusterService, vaultManager);
 
             for (IgniteComponent igniteComponent : components) {
                 igniteComponent.beforeNodeStop();
@@ -163,6 +131,10 @@ public class ItDistributedConfigurationStorageTest {
                 component.stop();
             }
         }
+
+        String name() {
+            return clusterService.topologyService().localMember().name();
+        }
     }
 
     /**
@@ -180,6 +152,8 @@ public class ItDistributedConfigurationStorageTest {
         try {
             node.start();
 
+            node.cmgManager.initCluster(List.of(node.name()), List.of());
+
             assertThat(node.cfgStorage.write(data, 0), willBe(equalTo(true)));
 
             waitForCondition(() -> Objects.nonNull(node.vaultManager.get(APPLIED_REV).join().value()), 3000);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/AbstractSchemaChangeTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/AbstractSchemaChangeTest.java
index e061ad2..3bacf58 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/AbstractSchemaChangeTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/AbstractSchemaChangeTest.java
@@ -33,6 +33,7 @@ import org.apache.ignite.Ignite;
 import org.apache.ignite.IgnitionManager;
 import org.apache.ignite.configuration.schemas.table.ColumnChange;
 import org.apache.ignite.internal.ItUtils;
+import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.internal.util.IgniteObjectName;
@@ -42,7 +43,6 @@ import org.apache.ignite.schema.SchemaBuilders;
 import org.apache.ignite.schema.definition.ColumnDefinition;
 import org.apache.ignite.schema.definition.ColumnType;
 import org.apache.ignite.schema.definition.TableDefinition;
-import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -83,7 +83,6 @@ abstract class AbstractSchemaChangeTest {
         nodesBootstrapCfg.put(
                 node0Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
                         + "  network: {\n"
                         + "    port: " + PORTS[0] + ",\n"
                         + "    nodeFinder: {\n"
@@ -96,7 +95,6 @@ abstract class AbstractSchemaChangeTest {
         nodesBootstrapCfg.put(
                 node1Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
                         + "  network: {\n"
                         + "    port: " + PORTS[1] + ",\n"
                         + "    nodeFinder: {\n"
@@ -109,7 +107,6 @@ abstract class AbstractSchemaChangeTest {
         nodesBootstrapCfg.put(
                 node2Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
                         + "  network: {\n"
                         + "    port: " + PORTS[2] + ",\n"
                         + "    nodeFinder: {\n"
@@ -132,7 +129,7 @@ abstract class AbstractSchemaChangeTest {
      * Check unsupported column type change.
      */
     @Test
-    public void testChangeColumnType() {
+    public void testChangeColumnType() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -163,7 +160,7 @@ abstract class AbstractSchemaChangeTest {
      * Check unsupported nullability change.
      */
     @Test
-    public void testChangeColumnsNullability() {
+    public void testChangeColumnsNullability() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -175,12 +172,15 @@ abstract class AbstractSchemaChangeTest {
     /**
      * Returns grid nodes.
      */
-    @NotNull
-    protected List<Ignite> startGrid() {
+    protected List<Ignite> startGrid() throws Exception {
         nodesBootstrapCfg.forEach((nodeName, configStr) ->
                 clusterNodes.add(IgnitionManager.start(nodeName, configStr, workDir.resolve(nodeName)))
         );
 
+        IgniteImpl metastorageNode = (IgniteImpl) clusterNodes.get(0);
+
+        metastorageNode.init(List.of(metastorageNode.name()));
+
         return clusterNodes;
     }
 
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItBaselineChangesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItBaselineChangesTest.java
index f2f5b96..6436f92 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItBaselineChangesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItBaselineChangesTest.java
@@ -29,10 +29,12 @@ import java.util.Set;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgnitionManager;
 import org.apache.ignite.internal.ItUtils;
+import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.lang.NodeStoppingException;
 import org.apache.ignite.schema.SchemaBuilders;
 import org.apache.ignite.schema.definition.ColumnType;
 import org.apache.ignite.schema.definition.TableDefinition;
@@ -41,6 +43,7 @@ import org.apache.ignite.table.Table;
 import org.apache.ignite.table.Tuple;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -48,6 +51,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
 /**
  * Test for baseline changes.
  */
+@Disabled("https://issues.apache.org/jira/browse/IGNITE-16471")
 @ExtendWith(WorkDirectoryExtension.class)
 public class ItBaselineChangesTest {
     /** Start network port for test nodes. */
@@ -66,24 +70,11 @@ public class ItBaselineChangesTest {
      */
     @BeforeEach
     void setUp(TestInfo testInfo) {
-        String node0Name = testNodeName(testInfo, BASE_PORT);
-        String node1Name = testNodeName(testInfo, BASE_PORT + 1);
-        String node2Name = testNodeName(testInfo, BASE_PORT + 2);
+        for (int i = 0; i < 3; ++i) {
+            String nodeName = testNodeName(testInfo, BASE_PORT + i);
 
-        initClusterNodes.put(
-                node0Name,
-                buildConfig(node0Name, 0)
-        );
-
-        initClusterNodes.put(
-                node1Name,
-                buildConfig(node0Name, 1)
-        );
-
-        initClusterNodes.put(
-                node2Name,
-                buildConfig(node0Name, 2)
-        );
+            initClusterNodes.put(nodeName, buildConfig(i));
+        }
     }
 
     /**
@@ -98,13 +89,17 @@ public class ItBaselineChangesTest {
      * Check dynamic table creation.
      */
     @Test
-    void testBaselineExtending(TestInfo testInfo) {
+    void testBaselineExtending(TestInfo testInfo) throws NodeStoppingException {
         initClusterNodes.forEach((nodeName, configStr) ->
                 clusterNodes.add(IgnitionManager.start(nodeName, configStr, workDir.resolve(nodeName)))
         );
 
         assertEquals(3, clusterNodes.size());
 
+        IgniteImpl metastorageNode = (IgniteImpl) clusterNodes.get(0);
+
+        metastorageNode.init(List.of(metastorageNode.name()));
+
         // Create table on node 0.
         TableDefinition schTbl1 = SchemaBuilders.tableBuilder("PUBLIC", "tbl1").columns(
                 SchemaBuilders.column("key", ColumnType.INT64).build(),
@@ -129,13 +124,11 @@ public class ItBaselineChangesTest {
         var node4Name = testNodeName(testInfo, nodePort(4));
 
         // Start 2 new nodes after
-        var node3 = IgnitionManager.start(
-                node3Name, buildConfig(metaStoreNode.name(), 3), workDir.resolve(node3Name));
+        var node3 = IgnitionManager.start(node3Name, buildConfig(3), workDir.resolve(node3Name));
 
         clusterNodes.add(node3);
 
-        var node4 = IgnitionManager.start(
-                node4Name, buildConfig(metaStoreNode.name(), 4), workDir.resolve(node4Name));
+        var node4 = IgnitionManager.start(node4Name, buildConfig(4), workDir.resolve(node4Name));
 
         clusterNodes.add(node4);
 
@@ -147,14 +140,13 @@ public class ItBaselineChangesTest {
 
         Table tbl4 = node4.tables().table(schTbl1.canonicalName());
 
-        final Tuple keyTuple1 = Tuple.create().set("key", 1L);
+        Tuple keyTuple1 = Tuple.create().set("key", 1L);
 
         assertEquals(1, (Long) tbl4.recordView().get(null, keyTuple1).value("key"));
     }
 
-    private String buildConfig(String metastoreNodeName, int nodeIdx) {
+    private static String buildConfig(int nodeIdx) {
         return "{\n"
-                + "  node.metastorageNodes: [ \"" + metastoreNodeName + "\" ],\n"
                 + "  network: {\n"
                 + "    port: " + nodePort(nodeIdx) + ",\n"
                 + "    nodeFinder: {\n"
@@ -164,7 +156,7 @@ public class ItBaselineChangesTest {
                 + "}";
     }
 
-    private int nodePort(int nodeIdx) {
+    private static int nodePort(int nodeIdx) {
         return BASE_PORT + nodeIdx;
     }
 }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItClusterInitTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItClusterInitTest.java
new file mode 100644
index 0000000..ee2b942
--- /dev/null
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItClusterInitTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.runner.app;
+
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.testframework.IgniteAbstractTest;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.NodeStoppingException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+
+/**
+ * Integration tests for initializing a cluster.
+ */
+public class ItClusterInitTest extends IgniteAbstractTest {
+    private final List<IgniteImpl> nodes = new ArrayList<>();
+
+    @AfterEach
+    void tearDown() throws Exception {
+        IgniteUtils.closeAll(nodes);
+    }
+
+    /**
+     * Tests a scenario when a cluster is initialized twice.
+     */
+    @Test
+    void testDoubleInit(TestInfo testInfo) throws NodeStoppingException {
+        createCluster(testInfo, 1);
+
+        IgniteImpl node = nodes.get(0);
+
+        node.init(List.of(node.name()));
+
+        assertThrows(IgniteInternalException.class, () -> node.init(List.of(node.name())));
+    }
+
+    /**
+     * Tests a scenario when some nodes are stopped during initialization.
+     */
+    @Test
+    void testInitStoppingNodes(TestInfo testInfo) {
+        createCluster(testInfo, 2);
+
+        IgniteImpl node1 = nodes.get(0);
+        IgniteImpl node2 = nodes.get(1);
+
+        node2.stop();
+
+        assertThrows(IgniteInternalException.class, () -> node1.init(List.of(node1.name(), node2.name())));
+
+        node1.stop();
+
+        assertThrows(NodeStoppingException.class, () -> node1.init(List.of(node1.name(), node2.name())));
+    }
+
+    private void createCluster(TestInfo testInfo, int numNodes) {
+        int[] ports = IntStream.range(3344, 3344 + numNodes).toArray();
+
+        String nodeFinderConfig = Arrays.stream(ports)
+                .mapToObj(port -> String.format("\"localhost:%d\"", port))
+                .collect(Collectors.joining(", ", "[", "]"));
+
+        Arrays.stream(ports)
+                .mapToObj(port -> {
+                    String config = "{"
+                            + " network.port: " + port + ","
+                            + " network.nodeFinder.netClusterNodes: " + nodeFinderConfig
+                            + "}";
+
+                    String nodeName = testNodeName(testInfo, port);
+
+                    return (IgniteImpl) IgnitionManager.start(nodeName, config, workDir.resolve(nodeName));
+                })
+                .forEach(nodes::add);
+    }
+}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItDataSchemaSyncTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItDataSchemaSyncTest.java
index 10209e7..21d0b91 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItDataSchemaSyncTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItDataSchemaSyncTest.java
@@ -22,15 +22,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
-import com.google.common.collect.Lists;
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.internal.ItUtils;
 import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.table.TableImpl;
 import org.apache.ignite.internal.test.WatchListenerInhibitor;
@@ -72,45 +71,34 @@ public class ItDataSchemaSyncTest extends IgniteAbstractTest {
     /**
      * Nodes bootstrap configuration.
      */
-    private static final Map<String, String> nodesBootstrapCfg = new LinkedHashMap<>() {
-        {
-            put("node0", "{\n"
-                    + "  \"node\": {\n"
-                    + "    \"metastorageNodes\":[ \"node0\" ]\n"
-                    + "  },\n"
+    private static final Map<String, String> nodesBootstrapCfg = Map.of(
+            "node0", "{\n"
                     + "  \"network\": {\n"
                     + "    \"port\":3344,\n"
                     + "    \"nodeFinder\": {\n"
                     + "      \"netClusterNodes\":[ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
                     + "    }\n"
                     + "  }\n"
-                    + "}");
+                    + "}",
 
-            put("node1", "{\n"
-                    + "  \"node\": {\n"
-                    + "    \"metastorageNodes\":[ \"node0\" ]\n"
-                    + "  },\n"
+            "node1", "{\n"
                     + "  \"network\": {\n"
                     + "    \"port\":3345,\n"
                     + "    \"nodeFinder\": {\n"
                     + "      \"netClusterNodes\":[ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
                     + "    }\n"
                     + "  }\n"
-                    + "}");
+                    + "}",
 
-            put("node2", "{\n"
-                    + "  \"node\": {\n"
-                    + "    \"metastorageNodes\":[ \"node0\" ]\n"
-                    + "  },\n"
+            "node2", "{\n"
                     + "  \"network\": {\n"
                     + "    \"port\":3346,\n"
                     + "    \"nodeFinder\": {\n"
                     + "      \"netClusterNodes\":[ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
                     + "    }\n"
                     + "  }\n"
-                    + "}");
-        }
-    };
+                    + "}"
+    );
 
     /**
      * Cluster nodes.
@@ -125,6 +113,10 @@ public class ItDataSchemaSyncTest extends IgniteAbstractTest {
         nodesBootstrapCfg.forEach((nodeName, configStr) ->
                 clusterNodes.add(IgnitionManager.start(nodeName, configStr, workDir.resolve(nodeName)))
         );
+
+        IgniteImpl metastorageNode = (IgniteImpl) clusterNodes.get(0);
+
+        metastorageNode.init(List.of(metastorageNode.name()));
     }
 
     /**
@@ -132,7 +124,7 @@ public class ItDataSchemaSyncTest extends IgniteAbstractTest {
      */
     @AfterEach
     void afterEach() throws Exception {
-        IgniteUtils.closeAll(Lists.reverse(clusterNodes));
+        IgniteUtils.closeAll(ItUtils.reverse(clusterNodes));
     }
 
     /**
@@ -142,7 +134,7 @@ public class ItDataSchemaSyncTest extends IgniteAbstractTest {
     @Test
     public void test() throws Exception {
         Ignite ignite0 = clusterNodes.get(0);
-        final IgniteImpl ignite1 = (IgniteImpl) clusterNodes.get(1);
+        IgniteImpl ignite1 = (IgniteImpl) clusterNodes.get(1);
 
         createTable(ignite0, SCHEMA, SHORT_TABLE_NAME);
 
@@ -197,7 +189,7 @@ public class ItDataSchemaSyncTest extends IgniteAbstractTest {
             );
         }
 
-        final CompletableFuture insertFut = IgniteTestUtils.runAsync(() ->
+        CompletableFuture insertFut = IgniteTestUtils.runAsync(() ->
                 table1.recordView().insert(
                         null,
                         Tuple.create()
@@ -207,11 +199,11 @@ public class ItDataSchemaSyncTest extends IgniteAbstractTest {
                                 .set("valStr2", "str2_" + 0)
                 ));
 
-        final CompletableFuture getFut = IgniteTestUtils.runAsync(() -> {
+        CompletableFuture getFut = IgniteTestUtils.runAsync(() -> {
             table1.recordView().get(null, Tuple.create().set("key", 10L));
         });
 
-        final CompletableFuture checkDefaultFut = IgniteTestUtils.runAsync(() -> {
+        CompletableFuture checkDefaultFut = IgniteTestUtils.runAsync(() -> {
             assertEquals("default",
                     table1.recordView().get(null, Tuple.create().set("key", 0L))
                             .value("valStr2"));
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItDynamicTableCreationTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItDynamicTableCreationTest.java
index fec8f27..18790b1 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItDynamicTableCreationTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItDynamicTableCreationTest.java
@@ -35,6 +35,7 @@ import org.apache.ignite.IgnitionManager;
 import org.apache.ignite.configuration.schemas.table.ColumnChange;
 import org.apache.ignite.configuration.validation.ConfigurationValidationException;
 import org.apache.ignite.internal.ItUtils;
+import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
@@ -47,9 +48,7 @@ import org.apache.ignite.table.KeyValueView;
 import org.apache.ignite.table.RecordView;
 import org.apache.ignite.table.Table;
 import org.apache.ignite.table.Tuple;
-import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
@@ -77,48 +76,21 @@ class ItDynamicTableCreationTest {
      */
     @BeforeEach
     void setUp(TestInfo testInfo) {
-        String node0Name = testNodeName(testInfo, PORTS[0]);
-        String node1Name = testNodeName(testInfo, PORTS[1]);
-        String node2Name = testNodeName(testInfo, PORTS[2]);
-
-        nodesBootstrapCfg.put(
-                node0Name,
-                "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
-                        + "  network: {\n"
-                        + "    port: " + PORTS[0] + ",\n"
-                        + "    nodeFinder:{\n"
-                        + "      netClusterNodes: [ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ] \n"
-                        + "    }\n"
-                        + "  }\n"
-                        + "}"
-        );
-
-        nodesBootstrapCfg.put(
-                node1Name,
-                "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
-                        + "  network: {\n"
-                        + "    port: " + PORTS[1] + ",\n"
-                        + "    nodeFinder:{\n"
-                        + "      netClusterNodes: [ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
-                        + "    }\n"
-                        + "  }\n"
-                        + "}"
-        );
-
-        nodesBootstrapCfg.put(
-                node2Name,
-                "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
-                        + "  network: {\n"
-                        + "    port: " + PORTS[2] + ",\n"
-                        + "    nodeFinder:{\n"
-                        + "      netClusterNodes: [ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
-                        + "    }\n"
-                        + "  }\n"
-                        + "}"
-        );
+        for (int i = 0; i < 3; ++i) {
+            String nodeName = testNodeName(testInfo, PORTS[i]);
+
+            nodesBootstrapCfg.put(
+                    nodeName,
+                    "{\n"
+                    + "  network: {\n"
+                    + "    port: " + PORTS[i] + ",\n"
+                    + "    nodeFinder:{\n"
+                    + "      netClusterNodes: [ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ] \n"
+                    + "    }\n"
+                    + "  }\n"
+                    + "}"
+            );
+        }
     }
 
     /**
@@ -132,14 +104,17 @@ class ItDynamicTableCreationTest {
     /**
      * Returns grid nodes.
      */
-    @NotNull
-    protected List<Ignite> startGrid() {
+    protected List<Ignite> startGrid() throws Exception {
         nodesBootstrapCfg.forEach((nodeName, configStr) ->
                 clusterNodes.add(IgnitionManager.start(nodeName, configStr, workDir.resolve(nodeName)))
         );
 
         assertEquals(3, clusterNodes.size());
 
+        IgniteImpl metastorageNode = (IgniteImpl) clusterNodes.get(0);
+
+        metastorageNode.init(List.of(metastorageNode.name()));
+
         return clusterNodes;
     }
 
@@ -147,7 +122,7 @@ class ItDynamicTableCreationTest {
      * Check dynamic table creation.
      */
     @Test
-    void testDynamicSimpleTableCreation() {
+    void testDynamicSimpleTableCreation() throws Exception {
         startGrid();
 
         // Create table on node 0.
@@ -175,8 +150,8 @@ class ItDynamicTableCreationTest {
         RecordView<Tuple> recView2 = tbl2.recordView();
         KeyValueView<Tuple, Tuple> kvView2 = tbl2.keyValueView();
 
-        final Tuple keyTuple1 = Tuple.create().set("key", 1L);
-        final Tuple keyTuple2 = Tuple.create().set("key", 2L);
+        Tuple keyTuple1 = Tuple.create().set("key", 1L);
+        Tuple keyTuple2 = Tuple.create().set("key", 2L);
 
         assertThrows(IllegalArgumentException.class, () -> kvView2.get(null, keyTuple1).value("key"));
         assertThrows(IllegalArgumentException.class, () -> kvView2.get(null, keyTuple2).value("key"));
@@ -196,7 +171,7 @@ class ItDynamicTableCreationTest {
      * Check dynamic table creation.
      */
     @Test
-    void testDynamicTableCreation() {
+    void testDynamicTableCreation() throws Exception {
         startGrid();
 
         // Create table on node 0.
@@ -218,8 +193,8 @@ class ItDynamicTableCreationTest {
                         .changeReplicas(1)
                         .changePartitions(10));
 
-        final UUID uuid = UUID.randomUUID();
-        final UUID uuid2 = UUID.randomUUID();
+        UUID uuid = UUID.randomUUID();
+        UUID uuid2 = UUID.randomUUID();
 
         // Put data on node 1.
         Table tbl1 = clusterNodes.get(1).tables().table(scmTbl1.canonicalName());
@@ -234,11 +209,11 @@ class ItDynamicTableCreationTest {
 
         // Get data on node 2.
         Table tbl2 = clusterNodes.get(2).tables().table(scmTbl1.canonicalName());
-        final RecordView<Tuple> recView2 = tbl2.recordView();
+        RecordView<Tuple> recView2 = tbl2.recordView();
         KeyValueView<Tuple, Tuple> kvView2 = tbl2.keyValueView();
 
-        final Tuple keyTuple1 = Tuple.create().set("key", uuid).set("affKey", 42L);
-        final Tuple keyTuple2 = Tuple.create().set("key", uuid2).set("affKey", 4242L);
+        Tuple keyTuple1 = Tuple.create().set("key", uuid).set("affKey", 42L);
+        Tuple keyTuple2 = Tuple.create().set("key", uuid2).set("affKey", 4242L);
 
         // KV view must NOT return key columns in value.
         assertThrows(IllegalArgumentException.class, () -> kvView2.get(null, keyTuple1).value("key"));
@@ -273,7 +248,7 @@ class ItDynamicTableCreationTest {
      * Check unsupported column type change.
      */
     @Test
-    public void testChangeColumnType() {
+    public void testChangeColumnType() throws Exception {
         List<Ignite> grid = startGrid();
 
         assertTableCreationFailed(grid, c -> c.changeType(t -> t.changeType("UNKNOWN_TYPE")));
@@ -297,11 +272,11 @@ class ItDynamicTableCreationTest {
 
     @Disabled("https://issues.apache.org/jira/browse/IGNITE-15747")
     @Test
-    void testMissedPk() {
+    void testMissedPk() throws Exception {
         List<Ignite> grid = startGrid();
 
         // Missed PK.
-        Assertions.assertThrows(ConfigurationValidationException.class, () -> {
+        assertThrows(ConfigurationValidationException.class, () -> {
             try {
                 grid.get(0).tables().createTable(
                         "PUBLIC.tbl1",
@@ -321,7 +296,7 @@ class ItDynamicTableCreationTest {
         });
 
         //Missed affinity cols.
-        Assertions.assertThrows(ConfigurationValidationException.class, () -> {
+        assertThrows(ConfigurationValidationException.class, () -> {
             try {
                 grid.get(0).tables().createTable(
                         "PUBLIC.tbl1",
@@ -342,7 +317,7 @@ class ItDynamicTableCreationTest {
         });
 
         //Missed key cols.
-        Assertions.assertThrows(ConfigurationValidationException.class, () -> {
+        assertThrows(ConfigurationValidationException.class, () -> {
             try {
                 grid.get(0).tables().createTable(
                         "PUBLIC.tbl1",
@@ -370,7 +345,7 @@ class ItDynamicTableCreationTest {
      * @param colChanger Column configuration changer.
      */
     private void assertTableCreationFailed(List<Ignite> grid, Consumer<ColumnChange> colChanger) {
-        Assertions.assertThrows(IgniteException.class, () -> {
+        assertThrows(IgniteException.class, () -> {
             try {
                 grid.get(0).tables().createTable(
                         "PUBLIC.tbl1",
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
index a5b34fe..d5a630d 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
@@ -153,9 +153,6 @@ public class ItIgniteNodeRestartTest extends IgniteAbstractTest {
         String nodeName = testNodeName(testInfo, 3344);
 
         Ignite ignite = IgnitionManager.start(nodeName, "{\n"
-                + "  \"node\": {\n"
-                + "    \"metastorageNodes\":[ " + nodeName + " ]\n"
-                + "  },\n"
                 + "  \"network\": {\n"
                 + "    \"port\":3344,\n"
                 + "    \"nodeFinder\": {\n"
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgnitionTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgnitionTest.java
index f10d512..9c23c76 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgnitionTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgnitionTest.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.internal.runner.app;
 
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
-import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -80,7 +79,6 @@ class ItIgnitionTest {
         nodesBootstrapCfg.put(
                 node0Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
                         + "  network: {\n"
                         + "    port: " + PORTS[0] + ",\n"
                         + "    nodeFinder: {\n"
@@ -93,7 +91,6 @@ class ItIgnitionTest {
         nodesBootstrapCfg.put(
                 node1Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
                         + "  network: {\n"
                         + "    port: " + PORTS[1] + ",\n"
                         + "    nodeFinder: {\n"
@@ -106,7 +103,6 @@ class ItIgnitionTest {
         nodesBootstrapCfg.put(
                 node2Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
                         + "  network: {\n"
                         + "    port: " + PORTS[2] + ",\n"
                         + "    nodeFinder: {\n"
@@ -150,99 +146,6 @@ class ItIgnitionTest {
     }
 
     /**
-     * Tests scenario when we try to start cluster with single node, but without any node, that hosts metastorage.
-     */
-    @Test
-    void testErrorWhenStartSingleNodeClusterWithoutMetastorage() {
-        try {
-            startedNodes.add(IgnitionManager.start("other-name", "{\n"
-                    + "    \"node\": {\n"
-                    + "        \"metastorageNodes\": [\n"
-                    + "            \"node-0\", \"node-1\", \"node-2\"\n"
-                    + "        ]\n"
-                    + "    },\n"
-                    + "    \"network\": {\n"
-                    + "        \"port\": 3344,\n"
-                    + "        \"nodeFinder\": {\n"
-                    + "          \"netClusterNodes\": [ \"localhost:3344\"] \n"
-                    + "        }\n"
-                    + "    }\n"
-                    + "}", workDir.resolve("other-name")));
-        } catch (Throwable th) {
-            assertTrue(IgniteTestUtils.hasCause(th,
-                    IgniteException.class,
-                    "Cannot start meta storage manager because there is no node in the cluster that hosts meta storage."
-            ));
-        }
-    }
-
-    /**
-     * Tests scenario when we try to start node that doesn't host metastorage in cluster with node, that hosts metastorage.
-     */
-    @Test
-    void testStartNodeClusterWithoutMetastorage() throws Exception {
-        Ignite ig1 = null;
-
-        Ignite ig2 = null;
-
-        try {
-            ig1 = IgnitionManager.start("node-0", "{\n"
-                    + "    \"node\": {\n"
-                    + "       \"metastorageNodes\":[ \"node-0\" ]\n"
-                    + "    },\n"
-                    + "    \"network\": {\n"
-                    + "      \"port\": 3344,\n"
-                    + "      \"nodeFinder\": {\n"
-                    + "        \"netClusterNodes\": [ \"localhost:3345\"]\n"
-                    + "      }\n"
-                    + "    }\n"
-                    + "}", workDir.resolve("node-0"));
-
-            ig2 = IgnitionManager.start("other-name", "{\n"
-                    + "    \"node\": {\n"
-                    + "        \"metastorageNodes\":[ \"node-0\" ]\n"
-                    + "    },\n"
-                    + "    \"network\": {\n"
-                    + "      \"port\": 3345,\n"
-                    + "      \"nodeFinder\": {\n"
-                    + "        \"netClusterNodes\": [ \"localhost:3344\"]\n"
-                    + "      }\n"
-                    + "    }\n"
-                    + "}", workDir.resolve("other-name"));
-
-            assertEquals(ig2.name(), "other-name");
-        } finally {
-            IgniteUtils.closeAll(ig2, ig1);
-        }
-    }
-
-    /**
-     * Tests scenario when we try to start single-node cluster with several metastorage nodes in config.
-     * TODO: test should be rewritten after init phase will be developed https://issues.apache.org/jira/browse/IGNITE-15114
-     */
-    @Test
-    void testStartNodeClusterWithTwoMetastorageInConfig() throws Exception {
-        try {
-            IgnitionManager.start("node-0", "{\n"
-                    + "    \"node\": {\n"
-                    + "        \"metastorageNodes\": [\n"
-                    + "            \"node-0\", \"node-1\", \"node-2\"\n"
-                    + "        ]\n"
-                    + "    },\n"
-                    + "    \"network\": {\n"
-                    + "      \"port\": 3344,\n"
-                    + "      \"nodeFinder\": {\n"
-                    + "        \"netClusterNodes\": [ \"localhost:3345\"]\n"
-                    + "      }\n"
-                    + "    }\n"
-                    + "}", workDir.resolve("node-0"));
-        } catch (IgniteException e) {
-            assertEquals(e.getCause().getMessage(), "Cannot start meta storage manager "
-                    + "because it is not allowed to start several metastorage nodes.");
-        }
-    }
-
-    /**
      * Tests scenario when we try to start node with invalid configuration.
      */
     @Test
@@ -272,7 +175,6 @@ class ItIgnitionTest {
         String nodeName = "node-url-config";
 
         String cfg = "{\n"
-                + "  node.metastorageNodes: [ \"" + nodeName + "\" ],\n"
                 + "  network: {\n"
                 + "    port: " + PORTS[0] + "\n"
                 + "  }\n"
@@ -293,7 +195,7 @@ class ItIgnitionTest {
      */
     private URL buildUrl(String path, String data) throws Exception {
         URLStreamHandler handler = new URLStreamHandler() {
-            private byte[] content = data.getBytes(StandardCharsets.UTF_8);
+            private final byte[] content = data.getBytes(StandardCharsets.UTF_8);
 
             @Override
             protected URLConnection openConnection(URL url) {
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java
index 5322e84..fd93d28 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java
@@ -21,19 +21,19 @@ import static java.util.stream.Collectors.joining;
 import static org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter.convert;
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
+import java.util.List;
 import java.util.Set;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.testframework.IgniteAbstractTest;
 import org.apache.ignite.internal.testframework.IgniteTestUtils;
 import org.apache.ignite.schema.SchemaBuilders;
 import org.apache.ignite.schema.definition.ColumnType;
 import org.apache.ignite.table.Table;
-import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
 
@@ -42,25 +42,25 @@ import org.junit.jupiter.api.TestInfo;
  */
 public class ItNoThreadsLeftTest extends IgniteAbstractTest {
     /** Schema name. */
-    public static final String SCHEMA = "PUBLIC";
+    private static final String SCHEMA = "PUBLIC";
 
     /** Short table name. */
-    public static final String SHORT_TABLE_NAME = "tbl1";
+    private static final String SHORT_TABLE_NAME = "tbl1";
 
-    /** Table name. */
-    public static final String TABLE_NAME = SCHEMA + "." + SHORT_TABLE_NAME;
+    private static final List<String> THREAD_NAMES_BLACKLIST = List.of(
+            "nioEventLoopGroup",
+            "globalEventExecutor",
+            "ForkJoinPool",
+            "process reaper",
+            "CompletableFutureDelayScheduler",
+            "parallel"
+    );
 
     /** One node cluster configuration. */
-    private static Function<String, String> NODE_CONFIGURATION = metastorageNodeName -> "{\n"
-            + "  \"node\": {\n"
-            + "    \"metastorageNodes\":[ " + metastorageNodeName + " ]\n"
-            + "  },\n"
-            + "  \"network\": {\n"
-            + "    \"port\":3344,\n"
-            + "    \"nodeFinder\": {\n"
-            + "      \"netClusterNodes\":[ \"localhost:3344\" ]\n"
-            + "    }\n"
-            + "  }\n"
+    private static final String NODE_CONFIGURATION =
+            "{\n"
+            + "  network.port: 3344,\n"
+            + "  network.nodeFinder.netClusterNodes: [ \"localhost:3344\" ]\n"
             + "}";
 
     /**
@@ -75,29 +75,34 @@ public class ItNoThreadsLeftTest extends IgniteAbstractTest {
 
         String nodeName = IgniteTestUtils.testNodeName(testInfo, 0);
 
-        Ignite ignite = IgnitionManager.start(
-                nodeName,
-                NODE_CONFIGURATION.apply(nodeName),
-                workDir.resolve(nodeName));
+        try (IgniteImpl ignite = (IgniteImpl) IgnitionManager.start(nodeName, NODE_CONFIGURATION, workDir.resolve(nodeName))) {
+            ignite.init(List.of(ignite.name()));
 
-        Table tbl = createTable(ignite, SCHEMA, SHORT_TABLE_NAME);
+            Table tbl = createTable(ignite, SCHEMA, SHORT_TABLE_NAME);
 
-        assertNotNull(tbl);
+            assertNotNull(tbl);
+        }
 
-        ignite.close();
+        boolean threadsKilled = waitForCondition(() -> threadsBefore.size() == getCurrentThreads().size(), 3_000);
 
-        assertTrue(waitForCondition(() -> threadsBefore.size() == getCurrentThreads().size(), 3_000),
-                getCurrentThreads().stream().filter(thread -> !threadsBefore.contains(thread)).map(Thread::getName).collect(joining(", ")));
+        if (!threadsKilled) {
+            String leakedThreadNames = getCurrentThreads().stream()
+                    .filter(thread -> !threadsBefore.contains(thread))
+                    .map(Thread::getName)
+                    .collect(joining(", "));
+
+            fail(leakedThreadNames);
+        }
     }
 
     /**
      * Creates a table.
      *
-     * @param node           Cluster node.
-     * @param schemaName     Schema name.
+     * @param node Cluster node.
+     * @param schemaName Schema name.
      * @param shortTableName Table name.
      */
-    protected Table createTable(Ignite node, String schemaName, String shortTableName) {
+    private static Table createTable(Ignite node, String schemaName, String shortTableName) {
         return node.tables().createTable(
                 schemaName + "." + shortTableName, tblCh -> convert(SchemaBuilders.tableBuilder(schemaName, shortTableName).columns(
                                 SchemaBuilders.column("key", ColumnType.INT64).build(),
@@ -114,16 +119,9 @@ public class ItNoThreadsLeftTest extends IgniteAbstractTest {
      *
      * @return Set of threads.
      */
-    @NotNull
-    private Set<Thread> getCurrentThreads() {
+    private static Set<Thread> getCurrentThreads() {
         return Thread.getAllStackTraces().keySet().stream()
-                .filter(thread ->
-                        !thread.getName().startsWith("nioEventLoopGroup")
-                                && !thread.getName().startsWith("globalEventExecutor")
-                                && !thread.getName().startsWith("ForkJoinPool")
-                                && !thread.getName().startsWith("process reaper")
-                                && !thread.getName().startsWith("CompletableFutureDelayScheduler")
-                                && !thread.getName().startsWith("parallel"))
+                .filter(thread -> THREAD_NAMES_BLACKLIST.stream().noneMatch(thread.getName()::startsWith))
                 .collect(Collectors.toSet());
     }
 }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItSchemaChangeKvViewTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItSchemaChangeKvViewTest.java
index 8063405..9688b89 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItSchemaChangeKvViewTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItSchemaChangeKvViewTest.java
@@ -41,7 +41,7 @@ class ItSchemaChangeKvViewTest extends AbstractSchemaChangeTest {
      * Check add a new column to table schema.
      */
     @Test
-    public void testDropColumn() {
+    public void testDropColumn() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -85,7 +85,7 @@ class ItSchemaChangeKvViewTest extends AbstractSchemaChangeTest {
      * Check drop column from table schema.
      */
     @Test
-    public void testAddNewColumn() {
+    public void testAddNewColumn() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -128,7 +128,7 @@ class ItSchemaChangeKvViewTest extends AbstractSchemaChangeTest {
      * Check rename column from table schema.
      */
     @Test
-    public void testRenameColumn() {
+    public void testRenameColumn() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -179,7 +179,7 @@ class ItSchemaChangeKvViewTest extends AbstractSchemaChangeTest {
      * Check merge table schema changes.
      */
     @Test
-    public void testMergeChangesAddDropAdd() {
+    public void testMergeChangesAddDropAdd() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -264,7 +264,7 @@ class ItSchemaChangeKvViewTest extends AbstractSchemaChangeTest {
      * Check merge table schema changes.
      */
     @Test
-    public void testMergeChangesColumnDefault() {
+    public void testMergeChangesColumnDefault() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -309,7 +309,7 @@ class ItSchemaChangeKvViewTest extends AbstractSchemaChangeTest {
      * Check an operation failed if an unknown column found.
      */
     @Test
-    public void testInsertRowOfDifferentSchema() {
+    public void testInsertRowOfDifferentSchema() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItSchemaChangeTableViewTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItSchemaChangeTableViewTest.java
index 4bda027..1f52c13 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItSchemaChangeTableViewTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItSchemaChangeTableViewTest.java
@@ -42,7 +42,7 @@ class ItSchemaChangeTableViewTest extends AbstractSchemaChangeTest {
      * Check add a new column to table schema.
      */
     @Test
-    public void testDropColumn() {
+    public void testDropColumn() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -79,7 +79,7 @@ class ItSchemaChangeTableViewTest extends AbstractSchemaChangeTest {
      * Check drop column from table schema.
      */
     @Test
-    public void testAddNewColumn() {
+    public void testAddNewColumn() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -116,7 +116,7 @@ class ItSchemaChangeTableViewTest extends AbstractSchemaChangeTest {
      * Check column renaming.
      */
     @Test
-    void testRenameColumn() {
+    void testRenameColumn() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -157,7 +157,7 @@ class ItSchemaChangeTableViewTest extends AbstractSchemaChangeTest {
      * Rename column then add a new column with same name.
      */
     @Test
-    void testRenameThenAddColumnWithSameName() {
+    void testRenameThenAddColumnWithSameName() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -198,7 +198,7 @@ class ItSchemaChangeTableViewTest extends AbstractSchemaChangeTest {
      * Check merge table schema changes.
      */
     @Test
-    public void testMergeChangesAddDropAdd() {
+    public void testMergeChangesAddDropAdd() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -272,7 +272,7 @@ class ItSchemaChangeTableViewTest extends AbstractSchemaChangeTest {
      * Check merge column default value changes.
      */
     @Test
-    public void testMergeChangesColumnDefault() {
+    public void testMergeChangesColumnDefault() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
@@ -317,7 +317,7 @@ class ItSchemaChangeTableViewTest extends AbstractSchemaChangeTest {
      * Check operation failed if unknown column found.
      */
     @Test
-    public void testStrictSchemaInsertRowOfNewSchema() {
+    public void testStrictSchemaInsertRowOfNewSchema() throws Exception {
         List<Ignite> grid = startGrid();
 
         createTable(grid);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTableCreationTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTableCreationTest.java
index 9cc9733..6ac26a2 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTableCreationTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTableCreationTest.java
@@ -72,7 +72,6 @@ class ItTableCreationTest {
         nodesBootstrapCfg.put(
                 node0Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\", \"" + node1Name + "\" ],\n"
                         + "  network: {\n"
                         + "    port: " + PORTS[0] + ",\n"
                         + "    nodeFinder: {\n"
@@ -160,7 +159,6 @@ class ItTableCreationTest {
         nodesBootstrapCfg.put(
                 node1Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\", \"" + node1Name + "\" ],\n"
                         + "  network: {\n"
                         + "    port: " + PORTS[1] + ",\n"
                         + "    nodeFinder: {\n"
@@ -173,7 +171,6 @@ class ItTableCreationTest {
         nodesBootstrapCfg.put(
                 node2Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\", \"" + node1Name + "\" ],\n"
                         + "  network: {\n"
                         + "    port: " + PORTS[2] + ",\n"
                         + "    nodeFinder: {\n"
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTablesApiTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTablesApiTest.java
index 9d896ec..7678a04 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTablesApiTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItTablesApiTest.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.runner.app;
 
+import static java.util.concurrent.CompletableFuture.runAsync;
+import static java.util.concurrent.CompletableFuture.supplyAsync;
 import static org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter.convert;
 import static org.apache.ignite.internal.test.WatchListenerInhibitor.metastorageEventsInhibitor;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -32,12 +34,12 @@ import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgnitionManager;
 import org.apache.ignite.internal.ItUtils;
+import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.table.IgniteTablesInternal;
 import org.apache.ignite.internal.table.TableImpl;
 import org.apache.ignite.internal.test.WatchListenerInhibitor;
@@ -74,45 +76,22 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     public static final String TABLE_NAME = SCHEMA + "." + SHORT_TABLE_NAME;
 
     /** Nodes bootstrap configuration. */
-    private final ArrayList<Function<String, String>> nodesBootstrapCfg = new ArrayList<>() {
-        {
-            add((metastorageNodeName) -> "{\n"
-                    + "  \"node\": {\n"
-                    + "    \"metastorageNodes\":[ " + metastorageNodeName + " ]\n"
-                    + "  },\n"
-                    + "  \"network\": {\n"
-                    + "    \"port\":3344,\n"
-                    + "    \"nodeFinder\": {\n"
-                    + "      \"netClusterNodes\":[ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
-                    + "    }\n"
-                    + "  }\n"
-                    + "}");
-
-            add((metastorageNodeName) -> "{\n"
-                    + "  \"node\": {\n"
-                    + "    \"metastorageNodes\":[ " + metastorageNodeName + " ]\n"
-                    + "  },\n"
-                    + "  \"network\": {\n"
-                    + "    \"port\":3345,\n"
-                    + "    \"nodeFinder\": {\n"
-                    + "      \"netClusterNodes\":[ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
-                    + "    }\n"
-                    + "  }\n"
-                    + "}");
-
-            add((metastorageNodeName) -> "{\n"
-                    + "  \"node\": {\n"
-                    + "    \"metastorageNodes\":[ " + metastorageNodeName + " ]\n"
-                    + "  },\n"
-                    + "  \"network\": {\n"
-                    + "    \"port\":3346,\n"
-                    + "    \"nodeFinder\": {\n"
-                    + "      \"netClusterNodes\":[ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
-                    + "    }\n"
-                    + "  }\n"
-                    + "}");
-        }
-    };
+    private final List<String> nodesBootstrapCfg = List.of(
+            "{\n"
+                    + "  network.port :3344,\n"
+                    + "  network.nodeFinder.netClusterNodes:[ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
+                    + "}",
+
+            "{\n"
+                    + "  network.port :3345,\n"
+                    + "  network.nodeFinder.netClusterNodes:[ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
+                    + "}",
+
+            "{\n"
+                    + "  network.port :3346,\n"
+                    + "  network.nodeFinder.netClusterNodes:[ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
+                    + "}"
+    );
 
     /** Cluster nodes. */
     private List<Ignite> clusterNodes;
@@ -121,20 +100,23 @@ public class ItTablesApiTest extends IgniteAbstractTest {
      * Before each.
      */
     @BeforeEach
-    void beforeEach(TestInfo testInfo) {
-        String metastorageNodeName = IgniteTestUtils.testNodeName(testInfo, 0);
-
-        clusterNodes = IntStream.range(0, nodesBootstrapCfg.size()).mapToObj(value -> {
+    void beforeEach(TestInfo testInfo) throws Exception {
+        clusterNodes = IntStream.range(0, nodesBootstrapCfg.size())
+                .mapToObj(value -> {
                     String nodeName = IgniteTestUtils.testNodeName(testInfo, value);
 
                     return IgnitionManager.start(
                             nodeName,
-                            nodesBootstrapCfg.get(value).apply(metastorageNodeName),
+                            nodesBootstrapCfg.get(value),
                             // Avoid a long file path name (260 characters) for windows.
                             workDir.resolve(Integer.toString(value))
                     );
-                }
-        ).collect(Collectors.toList());
+                })
+                .collect(Collectors.toList());
+
+        IgniteImpl metastorageNode = (IgniteImpl) clusterNodes.get(0);
+
+        metastorageNode.init(List.of(metastorageNode.name()));
     }
 
     /**
@@ -181,9 +163,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
 
         createTable(ignite0, SCHEMA, SHORT_TABLE_NAME);
 
-        CompletableFuture createTblFut = CompletableFuture.runAsync(() -> createTable(ignite1, SCHEMA, SHORT_TABLE_NAME));
-        CompletableFuture createTblIfNotExistsFut = CompletableFuture
-                .supplyAsync(() -> createTableIfNotExists(ignite1, SCHEMA, SHORT_TABLE_NAME));
+        CompletableFuture<Void> createTblFut = runAsync(() -> createTable(ignite1, SCHEMA, SHORT_TABLE_NAME));
+        CompletableFuture<Table> createTblIfNotExistsFut = supplyAsync(() -> createTableIfNotExists(ignite1, SCHEMA, SHORT_TABLE_NAME));
 
         for (Ignite ignite : clusterNodes) {
             if (ignite != ignite1) {
@@ -256,9 +237,7 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     }
 
     /**
-     * Trys to create an index which is already created.
-     *
-     * @throws Exception If failed.
+     * Tries to create an index which is already created.
      */
     @Test
     public void testAddIndex() {
@@ -297,8 +276,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
 
         addIndex(ignite0, SCHEMA, SHORT_TABLE_NAME);
 
-        CompletableFuture addIndesFut = CompletableFuture.runAsync(() -> addIndex(ignite1, SCHEMA, SHORT_TABLE_NAME));
-        CompletableFuture addIndesIfNotExistsFut = CompletableFuture.runAsync(() -> addIndexIfNotExists(ignite1, SCHEMA, SHORT_TABLE_NAME));
+        CompletableFuture<Void> addIndesFut = runAsync(() -> addIndex(ignite1, SCHEMA, SHORT_TABLE_NAME));
+        CompletableFuture<Void> addIndesIfNotExistsFut = runAsync(() -> addIndexIfNotExists(ignite1, SCHEMA, SHORT_TABLE_NAME));
 
         for (Ignite ignite : clusterNodes) {
             if (ignite != ignite1) {
@@ -365,8 +344,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
 
         addColumn(ignite0, SCHEMA, SHORT_TABLE_NAME);
 
-        CompletableFuture addColFut = CompletableFuture.runAsync(() -> addColumn(ignite1, SCHEMA, SHORT_TABLE_NAME));
-        CompletableFuture addColIfNotExistsFut = CompletableFuture.runAsync(() -> addColumnIfNotExists(ignite1, SCHEMA, SHORT_TABLE_NAME));
+        CompletableFuture<Void> addColFut = runAsync(() -> addColumn(ignite1, SCHEMA, SHORT_TABLE_NAME));
+        CompletableFuture<Void> addColIfNotExistsFut = runAsync(() -> addColumnIfNotExists(ignite1, SCHEMA, SHORT_TABLE_NAME));
 
         for (Ignite ignite : clusterNodes) {
             if (ignite != ignite1) {
@@ -394,9 +373,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     }
 
     /**
-     * Checks that if a table would be created/dropped in any cluster node, this action reflects on all others.
-     * Table management operations should pass in linearize order: if an action completed in one node,
-     * the result has to be visible to another one.
+     * Checks that if a table would be created/dropped in any cluster node, this action reflects on all others. Table management operations
+     * should pass in linearize order: if an action completed in one node, the result has to be visible to another one.
      *
      * @throws Exception If failed.
      */
@@ -414,11 +392,9 @@ public class ItTablesApiTest extends IgniteAbstractTest {
 
         UUID tblId = ((TableImpl) table).tableId();
 
-        CompletableFuture<Table> tableByNameFut = CompletableFuture.supplyAsync(() -> {
-            return ignite1.tables().table(TABLE_NAME);
-        });
+        CompletableFuture<Table> tableByNameFut = supplyAsync(() -> ignite1.tables().table(TABLE_NAME));
 
-        CompletableFuture<Table> tableByIdFut = CompletableFuture.supplyAsync(() -> {
+        CompletableFuture<Table> tableByIdFut = supplyAsync(() -> {
             try {
                 return ((IgniteTablesInternal) ignite1.tables()).table(tblId);
             } catch (NodeStoppingException e) {
@@ -466,8 +442,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     /**
      * Creates table.
      *
-     * @param node           Cluster node.
-     * @param schemaName     Schema name.
+     * @param node Cluster node.
+     * @param schemaName Schema name.
      * @param shortTableName Table name.
      */
     protected Table createTable(Ignite node, String schemaName, String shortTableName) {
@@ -486,8 +462,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     /**
      * Adds an index if it does not exist.
      *
-     * @param node           Cluster node.
-     * @param schemaName     Schema name.
+     * @param node Cluster node.
+     * @param schemaName Schema name.
      * @param shortTableName Table name.
      */
     protected Table createTableIfNotExists(Ignite node, String schemaName, String shortTableName) {
@@ -495,10 +471,10 @@ public class ItTablesApiTest extends IgniteAbstractTest {
             return node.tables().createTable(
                     schemaName + "." + shortTableName,
                     tblCh -> convert(SchemaBuilders.tableBuilder(schemaName, shortTableName).columns(Arrays.asList(
-                            SchemaBuilders.column("key", ColumnType.INT64).build(),
-                            SchemaBuilders.column("valInt", ColumnType.INT32).asNullable(true).build(),
-                            SchemaBuilders.column("valStr", ColumnType.string())
-                                    .withDefaultValueExpression("default").build()
+                                    SchemaBuilders.column("key", ColumnType.INT64).build(),
+                                    SchemaBuilders.column("valInt", ColumnType.INT32).asNullable(true).build(),
+                                    SchemaBuilders.column("valStr", ColumnType.string())
+                                            .withDefaultValueExpression("default").build()
                             )).withPrimaryKey("key").build(),
                             tblCh).changeReplicas(2).changePartitions(10)
             );
@@ -508,8 +484,7 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     }
 
     /**
-     * Drops the table which name is specified.
-     * If the table does not exist, an exception will be thrown.
+     * Drops the table which name is specified. If the table does not exist, an exception will be thrown.
      *
      * @param node Cluster node.
      * @param schemaName Schema name.
@@ -520,8 +495,7 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     }
 
     /**
-     * Drops the table which name is specified.
-     * If the table did not exist, a dropping would ignore.
+     * Drops the table which name is specified. If the table did not exist, a dropping would ignore.
      *
      * @param node Cluster node.
      * @param schemaName Schema name.
@@ -538,8 +512,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     /**
      * Adds an index.
      *
-     * @param node           Cluster node.
-     * @param schemaName     Schema name.
+     * @param node Cluster node.
+     * @param schemaName Schema name.
      * @param shortTableName Table name.
      */
     protected void addColumn(Ignite node, String schemaName, String shortTableName) {
@@ -552,10 +526,10 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     /**
      * Adds a column according to the column definition.
      *
-     * @param node           Ignite node.
-     * @param schemaName     Schema name.
+     * @param node Ignite node.
+     * @param schemaName Schema name.
      * @param shortTableName Table name.
-     * @param colDefinition  Column defenition.
+     * @param colDefinition Column defenition.
      */
     private void addColumnInternal(Ignite node, String schemaName, String shortTableName, ColumnDefinition colDefinition) {
         node.tables().alterTable(
@@ -572,8 +546,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     /**
      * Adds a column if it does not exist.
      *
-     * @param node           Ignite node.
-     * @param schemaName     Schema name.
+     * @param node Ignite node.
+     * @param schemaName Schema name.
      * @param shortTableName Table name.
      */
     protected void addColumnIfNotExists(Ignite node, String schemaName, String shortTableName) {
@@ -590,8 +564,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     /**
      * Adds a column.
      *
-     * @param node           Cluster node.
-     * @param schemaName     Schema name.
+     * @param node Cluster node.
+     * @param schemaName Schema name.
      * @param shortTableName Table name.
      */
     protected void addIndex(Ignite node, String schemaName, String shortTableName) {
@@ -613,8 +587,8 @@ public class ItTablesApiTest extends IgniteAbstractTest {
     /**
      * Creates a table if it does not exist.
      *
-     * @param node           Cluster node.
-     * @param schemaName     Schema name.
+     * @param node Cluster node.
+     * @param schemaName Schema name.
      * @param shortTableName Table name.
      */
     protected void addIndexIfNotExists(Ignite node, String schemaName, String shortTableName) {
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java
index 96c38c4..414c248 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java
@@ -17,11 +17,9 @@
 
 package org.apache.ignite.internal.runner.app;
 
-import java.net.InetSocketAddress;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -49,12 +47,8 @@ public class PlatformTestNodeRunner {
     private static final int RUN_TIME_MINUTES = 30;
 
     /** Nodes bootstrap configuration. */
-    private static final Map<String, String> nodesBootstrapCfg = new LinkedHashMap<>() {
-        {
-            put(NODE_NAME, "{\n"
-                    + "  \"node\": {\n"
-                    + "    \"metastorageNodes\":[ \"" + NODE_NAME + "\" ]\n"
-                    + "  },\n"
+    private static final Map<String, String> nodesBootstrapCfg = Map.of(
+            NODE_NAME, "{\n"
                     + "  \"clientConnector\":{\"port\": 10942,\"portRange\":10},"
                     + "  \"network\": {\n"
                     + "    \"port\":3344,\n"
@@ -62,9 +56,8 @@ public class PlatformTestNodeRunner {
                     + "      \"netClusterNodes\":[ \"localhost:3344\", \"localhost:3345\" ]\n"
                     + "    }\n"
                     + "  }\n"
-                    + "}");
-        }
-    };
+                    + "}"
+    );
 
     /** Base path for all temporary folders. */
     private static final Path BASE_PATH = Path.of("target", "work", "PlatformTestNodeRunner");
@@ -95,6 +88,10 @@ public class PlatformTestNodeRunner {
                 startedNodes.add(IgnitionManager.start(nodeName, configStr, BASE_PATH.resolve(nodeName)))
         );
 
+        IgniteImpl metastorageNode = (IgniteImpl) startedNodes.get(0);
+
+        metastorageNode.init(List.of(metastorageNode.name()));
+
         var keyCol = "key";
         var valCol = "val";
 
@@ -103,7 +100,7 @@ public class PlatformTestNodeRunner {
                 SchemaBuilders.column(valCol, ColumnType.string()).asNullable(true).build()
         ).withPrimaryKey(keyCol).build();
 
-        startedNodes.get(0).tables().createTable(schTbl.canonicalName(), tblCh ->
+        metastorageNode.tables().createTable(schTbl.canonicalName(), tblCh ->
                 SchemaConfigurationConverter.convert(schTbl, tblCh)
                         .changeReplicas(1)
                         .changePartitions(10)
@@ -127,6 +124,6 @@ public class PlatformTestNodeRunner {
      * @return Port number.
      */
     private static int getPort(IgniteImpl node) {
-        return ((InetSocketAddress) node.clientHandlerModule().localAddress()).getPort();
+        return node.clientAddress().port();
     }
 }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItAbstractThinClientTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItAbstractThinClientTest.java
index cf2cc80..a1d4cbe 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItAbstractThinClientTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItAbstractThinClientTest.java
@@ -19,8 +19,6 @@ package org.apache.ignite.internal.runner.app.client;
 
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
 
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
@@ -70,7 +68,7 @@ public abstract class ItAbstractThinClientTest extends IgniteAbstractTest {
      * Before each.
      */
     @BeforeAll
-    void beforeAll(TestInfo testInfo, @WorkDirectory Path workDir) {
+    void beforeAll(TestInfo testInfo, @WorkDirectory Path workDir) throws Exception {
         this.workDir = workDir;
 
         String node0Name = testNodeName(testInfo, 3344);
@@ -79,26 +77,16 @@ public abstract class ItAbstractThinClientTest extends IgniteAbstractTest {
         nodesBootstrapCfg.put(
                 node0Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
-                        + "  network: {\n"
-                        + "    port: " + 3344 + ",\n"
-                        + "    nodeFinder: {\n"
-                        + "      netClusterNodes: [ \"localhost:3344\", \"localhost:3345\" ]\n"
-                        + "    }\n"
-                        + "  }\n"
+                        + "  network.port: 3344,\n"
+                        + "  network.nodeFinder.netClusterNodes: [ \"localhost:3344\", \"localhost:3345\" ]\n"
                         + "}"
         );
 
         nodesBootstrapCfg.put(
                 node1Name,
                 "{\n"
-                        + "  node.metastorageNodes: [ \"" + node0Name + "\" ],\n"
-                        + "  network: {\n"
-                        + "    port: " + 3345 + ",\n"
-                        + "    nodeFinder: {\n"
-                        + "      netClusterNodes: [ \"localhost:3344\", \"localhost:3345\" ]\n"
-                        + "    }\n"
-                        + "  }\n"
+                        + "  network.port: 3345,\n"
+                        + "  network.nodeFinder.netClusterNodes: [ \"localhost:3344\", \"localhost:3345\" ]\n"
                         + "}"
         );
 
@@ -106,6 +94,10 @@ public abstract class ItAbstractThinClientTest extends IgniteAbstractTest {
                 startedNodes.add(IgnitionManager.start(nodeName, configStr, workDir.resolve(nodeName)))
         );
 
+        IgniteImpl metastorageNode = (IgniteImpl) startedNodes.get(0);
+
+        metastorageNode.init(List.of(metastorageNode.name()));
+
         TableDefinition schTbl = SchemaBuilders.tableBuilder(SCHEMA_NAME, TABLE_NAME).columns(
                 SchemaBuilders.column(COLUMN_KEY, ColumnType.INT32).build(),
                 SchemaBuilders.column(COLUMN_VAL, ColumnType.string()).asNullable(true).build()
@@ -142,8 +134,7 @@ public abstract class ItAbstractThinClientTest extends IgniteAbstractTest {
         List<String> res = new ArrayList<>(startedNodes.size());
 
         for (Ignite ignite : startedNodes) {
-            SocketAddress addr = ((IgniteImpl) ignite).clientHandlerModule().localAddress();
-            int port = ((InetSocketAddress) addr).getPort();
+            int port = ((IgniteImpl) ignite).clientAddress().port();
 
             res.add("127.0.0.1:" + port);
         }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/AbstractJdbcSelfTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/AbstractJdbcSelfTest.java
index 9e1ba8c..4117067 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/AbstractJdbcSelfTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/AbstractJdbcSelfTest.java
@@ -31,6 +31,9 @@ import java.util.List;
 import java.util.stream.Stream;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.lang.IgniteLogger;
 import org.junit.jupiter.api.AfterAll;
@@ -38,12 +41,13 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.function.Executable;
-import org.junit.jupiter.api.io.TempDir;
 
 /**
  * Abstract jdbc self test.
  */
+@ExtendWith(WorkDirectoryExtension.class)
 public class AbstractJdbcSelfTest {
     /** URL. */
     protected static final String URL = "jdbc:ignite:thin://127.0.0.1:10800";
@@ -63,15 +67,17 @@ public class AbstractJdbcSelfTest {
     /**
      * Creates a cluster of three nodes.
      *
-     * @param temp Temporal directory.
+     * @param temp Temporary directory.
      */
     @BeforeAll
-    public static void beforeAll(@TempDir Path temp, TestInfo testInfo) throws SQLException {
+    public static void beforeAll(@WorkDirectory Path temp, TestInfo testInfo) throws Exception {
         String nodeName = testNodeName(testInfo, 47500);
 
-        String configStr = "node.metastorageNodes: [ \"" + nodeName + "\" ]";
+        clusterNodes.add(IgnitionManager.start(nodeName, null, temp.resolve(nodeName)));
 
-        clusterNodes.add(IgnitionManager.start(nodeName, configStr, temp.resolve(nodeName)));
+        IgniteImpl metastorageNode = (IgniteImpl) clusterNodes.get(0);
+
+        metastorageNode.init(List.of(metastorageNode.name()));
 
         conn = DriverManager.getConnection(URL);
 
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/ItJdbcBatchSelfTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/ItJdbcBatchSelfTest.java
index 321e176..b04d0dd 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/ItJdbcBatchSelfTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/ItJdbcBatchSelfTest.java
@@ -65,7 +65,7 @@ public class ItJdbcBatchSelfTest extends AbstractJdbcSelfTest {
     private PreparedStatement pstmt;
 
     @BeforeAll
-    public static void beforeAll(@TempDir Path temp, TestInfo testInfo) throws SQLException {
+    public static void beforeAll(@TempDir Path temp, TestInfo testInfo) throws Exception {
         AbstractJdbcSelfTest.beforeAll(temp, testInfo);
 
         try (Statement statement = conn.createStatement()) {
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/AbstractBasicIntegrationTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/AbstractBasicIntegrationTest.java
index 53b8436..599c602 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/AbstractBasicIntegrationTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/AbstractBasicIntegrationTest.java
@@ -64,9 +64,6 @@ public class AbstractBasicIntegrationTest extends BaseIgniteAbstractTest {
 
     /** Nodes bootstrap configuration pattern. */
     private static final String NODE_BOOTSTRAP_CFG = "{\n"
-            + "  \"node\": {\n"
-            + "    \"metastorageNodes\":[ {} ]\n"
-            + "  },\n"
             + "  \"network\": {\n"
             + "    \"port\":{},\n"
             + "    \"nodeFinder\":{\n"
@@ -85,24 +82,23 @@ public class AbstractBasicIntegrationTest extends BaseIgniteAbstractTest {
     /**
      * Before all.
      *
-     * @param testInfo Test information oject.
+     * @param testInfo Test information object.
      */
     @BeforeAll
-    void startNodes(TestInfo testInfo) {
-        //TODO: IGNITE-16034 Here we assume that Metastore consists into one node, and it starts at first.
-        String metastorageNodes = '\"' + IgniteTestUtils.testNodeName(testInfo, 0) + '\"';
-
+    void startNodes(TestInfo testInfo) throws Exception {
         String connectNodeAddr = "\"localhost:" + BASE_PORT + '\"';
 
         for (int i = 0; i < nodes(); i++) {
             String curNodeName = IgniteTestUtils.testNodeName(testInfo, i);
 
-            CLUSTER_NODES.add(IgnitionManager.start(curNodeName, IgniteStringFormatter.format(NODE_BOOTSTRAP_CFG,
-                    metastorageNodes,
-                    BASE_PORT + i,
-                    connectNodeAddr
-            ), WORK_DIR.resolve(curNodeName)));
+            String config = IgniteStringFormatter.format(NODE_BOOTSTRAP_CFG, BASE_PORT + i, connectNodeAddr);
+
+            CLUSTER_NODES.add(IgnitionManager.start(curNodeName, config, WORK_DIR.resolve(curNodeName)));
         }
+
+        IgniteImpl metastorageNode = (IgniteImpl) CLUSTER_NODES.get(0);
+
+        metastorageNode.init(List.of(metastorageNode.name()));
     }
 
     /**
diff --git a/modules/runner/src/integrationTest/resources/ignite-config.json b/modules/runner/src/integrationTest/resources/ignite-config.json
index 6a2ca12..8698d5a 100644
--- a/modules/runner/src/integrationTest/resources/ignite-config.json
+++ b/modules/runner/src/integrationTest/resources/ignite-config.json
@@ -1,9 +1,4 @@
 {
-    "node": {
-        "metastorageNodes": [
-            "node1"
-        ]
-    },
     "network": {
         "port": 3344,
         "portRange": 10,
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 38bc296..d697389 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -22,6 +22,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
@@ -37,6 +38,8 @@ import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
 import org.apache.ignite.configuration.schemas.store.DataStorageConfiguration;
 import org.apache.ignite.configuration.schemas.table.TablesConfiguration;
 import org.apache.ignite.internal.baseline.BaselineManager;
+import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
+import org.apache.ignite.internal.cluster.management.messages.CmgMessagesSerializationRegistryInitializer;
 import org.apache.ignite.internal.components.LongJvmPauseDetector;
 import org.apache.ignite.internal.compute.ComputeComponent;
 import org.apache.ignite.internal.compute.ComputeComponentImpl;
@@ -48,7 +51,6 @@ import org.apache.ignite.internal.configuration.ConfigurationModules;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
 import org.apache.ignite.internal.configuration.rest.ConfigurationHttpHandlers;
-import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.DistributedConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.LocalConfigurationStorage;
 import org.apache.ignite.internal.manager.IgniteComponent;
@@ -77,10 +79,12 @@ import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.network.ClusterService;
 import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NettyBootstrapFactory;
+import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.scalecube.ScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.table.manager.IgniteTables;
 import org.apache.ignite.tx.IgniteTransactions;
+import org.intellij.lang.annotations.Language;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.TestOnly;
@@ -148,6 +152,8 @@ public class IgniteImpl implements Ignite {
     /** Rest module. */
     private final RestComponent restComponent;
 
+    private final ClusterManagementGroupManager cmgMgr;
+
     /** Client handler module. */
     private final ClientHandlerModule clientHandlerModule;
 
@@ -163,17 +169,12 @@ public class IgniteImpl implements Ignite {
     /**
      * The Constructor.
      *
-     * @param name                       Ignite node name.
-     * @param workDir                    Work directory for the started node. Must not be {@code null}.
-     * @param serviceProviderClassLoader The class loader to be used to load provider-configuration files and provider
-     *                                   classes, or {@code null} if the system class loader (or, failing that
-     *                                   the bootstrap class loader) is to be used
+     * @param name Ignite node name.
+     * @param workDir Work directory for the started node. Must not be {@code null}.
+     * @param serviceProviderClassLoader The class loader to be used to load provider-configuration files and provider classes, or
+     *      {@code null} if the system class loader (or, failing that the bootstrap class loader) is to be used.
      */
-    IgniteImpl(
-            String name,
-            Path workDir,
-            ClassLoader serviceProviderClassLoader
-    ) {
+    IgniteImpl(String name, Path workDir, ClassLoader serviceProviderClassLoader) {
         this.name = name;
 
         vaultMgr = createVault(workDir);
@@ -191,6 +192,8 @@ public class IgniteImpl implements Ignite {
         NetworkConfiguration networkConfiguration = nodeCfgMgr.configurationRegistry().getConfiguration(NetworkConfiguration.KEY);
 
         MessageSerializationRegistryImpl serializationRegistry = new MessageSerializationRegistryImpl();
+
+        CmgMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
         RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
         SqlQueryMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
         TxMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
@@ -215,26 +218,24 @@ public class IgniteImpl implements Ignite {
 
         txManager = new TableTxManagerImpl(clusterSvc, new HeapLockManager());
 
+        cmgMgr = new ClusterManagementGroupManager(clusterSvc, raftMgr, restComponent);
+
         metaStorageMgr = new MetaStorageManager(
                 vaultMgr,
-                nodeCfgMgr,
                 clusterSvc,
+                cmgMgr,
                 raftMgr,
                 new RocksDbKeyValueStorage(workDir.resolve(METASTORAGE_DB_PATH))
         );
 
-        ConfigurationStorage cfgStorage = new DistributedConfigurationStorage(metaStorageMgr, vaultMgr);
-
         clusterCfgMgr = new ConfigurationManager(
                 modules.distributed().rootKeys(),
                 modules.distributed().validators(),
-                cfgStorage,
+                new DistributedConfigurationStorage(metaStorageMgr, vaultMgr),
                 modules.distributed().internalSchemaExtensions(),
                 modules.distributed().polymorphicSchemaExtensions()
         );
 
-        new ConfigurationHttpHandlers(nodeCfgMgr, clusterCfgMgr).registerHandlers(restComponent);
-
         baselineMgr = new BaselineManager(
                 clusterCfgMgr,
                 metaStorageMgr,
@@ -274,10 +275,12 @@ public class IgniteImpl implements Ignite {
                 nettyBootstrapFactory
         );
 
+        new ConfigurationHttpHandlers(nodeCfgMgr, clusterCfgMgr).registerHandlers(restComponent);
+
         longJvmPauseDetector = new LongJvmPauseDetector(name);
     }
 
-    private ConfigurationModules loadConfigurationModules(ClassLoader classLoader) {
+    private static ConfigurationModules loadConfigurationModules(ClassLoader classLoader) {
         var modulesProvider = new ServiceLoaderModulesProvider();
         List<ConfigurationModule> modules = modulesProvider.modules(classLoader);
 
@@ -303,22 +306,21 @@ public class IgniteImpl implements Ignite {
     /**
      * Starts ignite node.
      *
-     * @param cfg Optional node configuration based on {@link org.apache.ignite.configuration.schemas.runner.NodeConfigurationSchema} and
-     *            {@link org.apache.ignite.configuration.schemas.network.NetworkConfigurationSchema}. Following rules are used for applying
-     *            the configuration properties:
-     *            <ol>
-     *            <li>Specified property overrides existing one or just applies itself if it wasn't
-     *            previously specified.</li>
-     *            <li>All non-specified properties either use previous value or use default one from
-     *            corresponding configuration schema.</li>
-     *            </ol>
-     *            So that, in case of initial node start (first start ever) specified configuration, supplemented with defaults, is
-     *            used. If no configuration was provided defaults are used for all configuration properties. In case of node
-     *            restart, specified properties override existing ones, non specified properties that also weren't specified
-     *            previously use default values. Please pay attention that previously specified properties are searched in the
-     *            {@code workDir} specified by the user.
+     * @param cfg Optional node configuration based on {@link org.apache.ignite.configuration.schemas.network.NetworkConfigurationSchema}.
+     *      Following rules are used for applying the configuration properties:
+     *      <ol>
+     *      <li>Specified property overrides existing one or just applies itself if it wasn't
+     *      previously specified.</li>
+     *      <li>All non-specified properties either use previous value or use default one from
+     *      corresponding configuration schema.</li>
+     *      </ol>
+     *      So that, in case of initial node start (first start ever) specified configuration, supplemented with defaults, is
+     *      used. If no configuration was provided defaults are used for all configuration properties. In case of node
+     *      restart, specified properties override existing ones, non specified properties that also weren't specified
+     *      previously use default values. Please pay attention that previously specified properties are searched in the
+     *      {@code workDir} specified by the user.
      */
-    public void start(@Nullable String cfg) {
+    public void start(@Language("HOCON") @Nullable String cfg) {
         List<IgniteComponent> startedComponents = new ArrayList<>();
 
         try {
@@ -338,10 +340,7 @@ public class IgniteImpl implements Ignite {
             vaultMgr.putName(name).join();
 
             // Node configuration manager startup.
-            doStartComponent(
-                    name,
-                    startedComponents,
-                    nodeCfgMgr);
+            doStartComponent(name, startedComponents, nodeCfgMgr);
 
             // Node configuration manager bootstrap.
             if (cfg != null) {
@@ -358,15 +357,16 @@ public class IgniteImpl implements Ignite {
             List<IgniteComponent> otherComponents = List.of(
                     nettyBootstrapFactory,
                     clusterSvc,
+                    restComponent,
                     computeComponent,
                     raftMgr,
                     txManager,
-                    metaStorageMgr,
                     clusterCfgMgr,
+                    cmgMgr,
+                    metaStorageMgr,
                     baselineMgr,
                     distributedTblMgr,
                     qryEngine,
-                    restComponent,
                     clientHandlerModule
             );
 
@@ -399,7 +399,8 @@ public class IgniteImpl implements Ignite {
     public void stop() {
         if (status.getAndSet(Status.STOPPING) == Status.STARTED) {
             doStopNode(List.of(longJvmPauseDetector, vaultMgr, nodeCfgMgr, clusterSvc, computeComponent, raftMgr, txManager, metaStorageMgr,
-                    clusterCfgMgr, baselineMgr, distributedTblMgr, qryEngine, restComponent, clientHandlerModule, nettyBootstrapFactory));
+                    clusterCfgMgr, cmgMgr, baselineMgr, distributedTblMgr, qryEngine, restComponent, clientHandlerModule,
+                    nettyBootstrapFactory));
         }
     }
 
@@ -465,27 +466,62 @@ public class IgniteImpl implements Ignite {
     }
 
     /**
-     * Returns client handler module.
+     * Returns the id of the current node.
      */
-    public ClientHandlerModule clientHandlerModule() {
-        return clientHandlerModule;
+    // TODO: should be encapsulated in local properties, see https://issues.apache.org/jira/browse/IGNITE-15131
+    public String id() {
+        return clusterSvc.topologyService().localMember().id();
     }
 
     /**
-     * Returns the id of the current node.
+     * Returns the local address of REST endpoints.
+     *
+     * @throws IgniteInternalException if the REST module is not started.
      */
-    public String id() {
-        return clusterSvc.topologyService().localMember().id();
+    // TODO: should be encapsulated in local properties, see https://issues.apache.org/jira/browse/IGNITE-15131
+    public NetworkAddress restAddress() {
+        return NetworkAddress.from(restComponent.localAddress());
+    }
+
+    /**
+     * Returns the local address of the Thin Client.
+     *
+     * @throws IgniteInternalException if the Client module is not started.
+     */
+    // TODO: should be encapsulated in local properties, see https://issues.apache.org/jira/browse/IGNITE-15131
+    public NetworkAddress clientAddress() {
+        return NetworkAddress.from(clientHandlerModule.localAddress());
+    }
+
+    /**
+     * Initializes the cluster that this node is present in.
+     *
+     * @param metaStorageNodeNames names of nodes that will host the Meta Storage and the CMG.
+     * @throws NodeStoppingException If node stopping intention was detected.
+     */
+    public void init(Collection<String> metaStorageNodeNames) throws NodeStoppingException {
+        init(metaStorageNodeNames, List.of());
+    }
+
+    /**
+     * Initializes the cluster that this node is present in.
+     *
+     * @param metaStorageNodeNames names of nodes that will host the Meta Storage.
+     * @param cmgNodeNames names of nodes that will host the CMG.
+     * @throws NodeStoppingException If node stopping intention was detected.
+     */
+    public void init(Collection<String> metaStorageNodeNames, Collection<String> cmgNodeNames) throws NodeStoppingException {
+        cmgMgr.initCluster(metaStorageNodeNames, cmgNodeNames);
     }
 
     /**
      * Checks node status. If it's {@link Status#STOPPING} then prevents further starting and throws NodeStoppingException that will lead to
      * stopping already started components later on, otherwise starts component and add it to started components list.
      *
-     * @param nodeName          Node name.
+     * @param nodeName Node name.
      * @param startedComponents List of already started components for given node.
-     * @param component         Ignite component to start.
-     * @param <T>               Ignite component type.
+     * @param component Ignite component to start.
+     * @param <T> Ignite component type.
      * @throws NodeStoppingException If node stopping intention was detected.
      */
     private <T extends IgniteComponent> void doStartComponent(
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/CoreDistributedConfigurationModule.java b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/CoreDistributedConfigurationModule.java
index fb97540..185940f 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/CoreDistributedConfigurationModule.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/CoreDistributedConfigurationModule.java
@@ -21,7 +21,6 @@ import java.util.Collection;
 import java.util.List;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
-import org.apache.ignite.configuration.schemas.runner.ClusterConfiguration;
 import org.apache.ignite.configuration.schemas.store.DataStorageConfiguration;
 import org.apache.ignite.configuration.schemas.store.PageMemoryDataRegionConfigurationSchema;
 import org.apache.ignite.configuration.schemas.store.RocksDbDataRegionConfigurationSchema;
@@ -45,7 +44,6 @@ public class CoreDistributedConfigurationModule implements ConfigurationModule {
     @Override
     public Collection<RootKey<?, ?>> rootKeys() {
         return List.of(
-                ClusterConfiguration.KEY,
                 TablesConfiguration.KEY,
                 DataStorageConfiguration.KEY
         );
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/CoreLocalConfigurationModule.java b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/CoreLocalConfigurationModule.java
index 8ef320e..7363930 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/CoreLocalConfigurationModule.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/CoreLocalConfigurationModule.java
@@ -25,7 +25,6 @@ import org.apache.ignite.configuration.schemas.clientconnector.ClientConnectorCo
 import org.apache.ignite.configuration.schemas.compute.ComputeConfiguration;
 import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
 import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
-import org.apache.ignite.configuration.schemas.runner.NodeConfiguration;
 
 /**
  * {@link ConfigurationModule} for node-local configuration provided by ignite-api.
@@ -42,7 +41,6 @@ public class CoreLocalConfigurationModule implements ConfigurationModule {
     public Collection<RootKey<?, ?>> rootKeys() {
         return List.of(
                 NetworkConfiguration.KEY,
-                NodeConfiguration.KEY,
                 RestConfiguration.KEY,
                 ClientConnectorConfiguration.KEY,
                 ComputeConfiguration.KEY
diff --git a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/CoreDistributedConfigurationModuleTest.java b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/CoreDistributedConfigurationModuleTest.java
index 0672b12..53bae42 100644
--- a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/CoreDistributedConfigurationModuleTest.java
+++ b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/CoreDistributedConfigurationModuleTest.java
@@ -28,7 +28,6 @@ import static org.hamcrest.Matchers.is;
 import java.util.Optional;
 import java.util.ServiceLoader;
 import java.util.ServiceLoader.Provider;
-import org.apache.ignite.configuration.schemas.runner.ClusterConfiguration;
 import org.apache.ignite.configuration.schemas.store.DataStorageConfiguration;
 import org.apache.ignite.configuration.schemas.table.HashIndexConfigurationSchema;
 import org.apache.ignite.configuration.schemas.table.PartialIndexConfigurationSchema;
@@ -48,11 +47,6 @@ class CoreDistributedConfigurationModuleTest {
     }
 
     @Test
-    void hasClusterConfigurationRoot() {
-        assertThat(module.rootKeys(), hasItem(ClusterConfiguration.KEY));
-    }
-
-    @Test
     void hasTablesConfigurationRoot() {
         assertThat(module.rootKeys(), hasItem(TablesConfiguration.KEY));
     }
diff --git a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/CoreLocalConfigurationModuleTest.java b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/CoreLocalConfigurationModuleTest.java
index 75c87d7..45f8b79 100644
--- a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/CoreLocalConfigurationModuleTest.java
+++ b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/CoreLocalConfigurationModuleTest.java
@@ -32,7 +32,6 @@ import org.apache.ignite.configuration.schemas.clientconnector.ClientConnectorCo
 import org.apache.ignite.configuration.schemas.compute.ComputeConfiguration;
 import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
 import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
-import org.apache.ignite.configuration.schemas.runner.NodeConfiguration;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -52,11 +51,6 @@ class CoreLocalConfigurationModuleTest {
     }
 
     @Test
-    void hasNodeConfigurationRoot() {
-        assertThat(module.rootKeys(), hasItem(NodeConfiguration.KEY));
-    }
-
-    @Test
     void hasRestConfigurationRoot() {
         assertThat(module.rootKeys(), hasItem(RestConfiguration.KEY));
     }
diff --git a/parent/pom.xml b/parent/pom.xml
index 911c381..8d23767 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -112,6 +112,18 @@
         <maven.shade.plugin.version>3.2.4</maven.shade.plugin.version>
         <maven.source.plugin.version>3.2.1</maven.source.plugin.version>
         <maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>
+
+        <argLine>
+            --add-opens java.base/java.lang=ALL-UNNAMED
+            --add-opens java.base/java.lang.invoke=ALL-UNNAMED
+            --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+            --add-opens java.base/java.io=ALL-UNNAMED
+            --add-opens java.base/java.nio=ALL-UNNAMED
+            --add-opens java.base/java.util=ALL-UNNAMED
+            --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
+            -Dio.netty.tryReflectionSetAccessible=true
+            -Djava.util.logging.config.file=../../config/java.util.logging.properties
+        </argLine>
     </properties>
 
     <distributionManagement>
@@ -197,6 +209,12 @@
 
             <dependency>
                 <groupId>org.apache.ignite</groupId>
+                <artifactId>ignite-cluster-management</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.ignite</groupId>
                 <artifactId>ignite-metastorage</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -1093,17 +1111,6 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
-                    <argLine>
-                        --add-opens java.base/java.lang=ALL-UNNAMED
-                        --add-opens java.base/java.lang.invoke=ALL-UNNAMED
-                        --add-opens java.base/java.lang.reflect=ALL-UNNAMED
-                        --add-opens java.base/java.io=ALL-UNNAMED
-                        --add-opens java.base/java.nio=ALL-UNNAMED
-                        --add-opens java.base/java.util=ALL-UNNAMED
-                        --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-                        -Dio.netty.tryReflectionSetAccessible=true
-                        -Djava.util.logging.config.file=../../config/java.util.logging.properties
-                    </argLine>
                     <excludes>
                         <exclude>%regex[.*(It)[A-Z].*]</exclude>
                         <!-- Exclude inner classes (preserve default behaviour) -->
@@ -1119,17 +1126,7 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-failsafe-plugin</artifactId>
                 <configuration>
-                    <argLine>
-                        --add-opens java.base/java.lang=ALL-UNNAMED
-                        --add-opens java.base/java.lang.invoke=ALL-UNNAMED
-                        --add-opens java.base/java.lang.reflect=ALL-UNNAMED
-                        --add-opens java.base/java.io=ALL-UNNAMED
-                        --add-opens java.base/java.nio=ALL-UNNAMED
-                        --add-opens java.base/java.util=ALL-UNNAMED
-                        --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-                        -Dio.netty.tryReflectionSetAccessible=true
-                        -Djava.util.logging.config.file=../../config/java.util.logging.properties
-                    </argLine>
+
                     <useModulePath>false</useModulePath>
                     <includes>
                         <include>%regex[.*(It)[A-Z].*]</include>
diff --git a/pom.xml b/pom.xml
index 233be44..2e5661f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,7 @@
         <module>modules/client</module>
         <module>modules/client-common</module>
         <module>modules/client-handler</module>
+        <module>modules/cluster-management</module>
         <module>modules/compute</module>
         <module>modules/configuration</module>
         <module>modules/configuration-annotation-processor</module>