99
1010package org .elasticsearch .cluster .coordination ;
1111
12+ import org .elasticsearch .TransportVersion ;
1213import org .elasticsearch .cluster .ClusterModule ;
14+ import org .elasticsearch .cluster .ClusterState ;
1315import org .elasticsearch .cluster .metadata .IndexGraveyard ;
1416import org .elasticsearch .cluster .metadata .IndexMetadata ;
1517import org .elasticsearch .cluster .metadata .Metadata ;
1618import org .elasticsearch .common .UUIDs ;
17- import org .elasticsearch .common .bytes .BytesReference ;
19+ import org .elasticsearch .common .settings .ClusterSettings ;
20+ import org .elasticsearch .common .settings .Settings ;
21+ import org .elasticsearch .core .Tuple ;
22+ import org .elasticsearch .env .Environment ;
23+ import org .elasticsearch .env .TestEnvironment ;
24+ import org .elasticsearch .gateway .PersistedClusterStateService ;
1825import org .elasticsearch .index .Index ;
1926import org .elasticsearch .indices .IndicesModule ;
2027import org .elasticsearch .test .ESTestCase ;
28+ import org .elasticsearch .test .TestClusterCustomMetadata ;
29+ import org .elasticsearch .test .TestProjectCustomMetadata ;
2130import org .elasticsearch .xcontent .NamedXContentRegistry ;
22- import org .elasticsearch .xcontent .XContentBuilder ;
23- import org .elasticsearch .xcontent .XContentParser ;
24- import org .elasticsearch .xcontent .XContentParserConfiguration ;
25- import org .elasticsearch .xcontent .json .JsonXContent ;
31+ import org .elasticsearch .xcontent .ParseField ;
2632
2733import java .io .IOException ;
2834import java .nio .file .Path ;
35+ import java .util .EnumSet ;
2936import java .util .List ;
3037import java .util .function .Function ;
3138import java .util .stream .Stream ;
3441import static org .elasticsearch .cluster .metadata .DataStreamTestHelper .newInstance ;
3542import static org .hamcrest .Matchers .equalTo ;
3643import static org .hamcrest .Matchers .instanceOf ;
37- import static org .hamcrest .Matchers .not ;
3844
3945public class ElasticsearchNodeCommandTests extends ESTestCase {
4046
41- public void testLoadStateWithoutMissingCustoms () throws IOException {
42- runLoadStateTest (false , false );
43- }
44-
4547 public void testLoadStateWithoutMissingCustomsButPreserved () throws IOException {
46- runLoadStateTest (false , true );
48+ runLoadStateTest (false );
4749 }
4850
4951 public void testLoadStateWithMissingCustomsButPreserved () throws IOException {
50- runLoadStateTest (true , true );
52+ runLoadStateTest (true );
5153 }
5254
53- public void testLoadStateWithMissingCustomsAndNotPreserved () throws IOException {
54- runLoadStateTest (true , false );
55+ @ Override
56+ public Settings buildEnvSettings (Settings settings ) {
57+ // we control the data path in the tests, so we don't need to set it here
58+ return settings ;
5559 }
5660
57- private void runLoadStateTest (boolean hasMissingCustoms , boolean preserveUnknownCustoms ) throws IOException {
58- final Metadata latestMetadata = randomMeta ();
59- final XContentBuilder builder = JsonXContent .contentBuilder ();
60- builder .startObject ();
61- Metadata .FORMAT .toXContent (builder , latestMetadata );
62- builder .endObject ();
63-
64- XContentParserConfiguration parserConfig = hasMissingCustoms
65- ? parserConfig ().withRegistry (ElasticsearchNodeCommand .namedXContentRegistry )
66- : parserConfig ();
67- Metadata loadedMetadata ;
68- try (XContentParser parser = createParser (parserConfig , JsonXContent .jsonXContent , BytesReference .bytes (builder ))) {
69- loadedMetadata = Metadata .fromXContent (parser );
70- }
71- assertThat (loadedMetadata .clusterUUID (), not (equalTo ("_na_" )));
72- assertThat (loadedMetadata .clusterUUID (), equalTo (latestMetadata .clusterUUID ()));
73- assertThat (loadedMetadata .getProject ().dataStreams (), equalTo (latestMetadata .getProject ().dataStreams ()));
61+ private void runLoadStateTest (boolean hasMissingCustoms ) throws IOException {
62+ final var dataPath = createTempDir ();
63+ final Settings settings = Settings .builder ()
64+ .putList (Environment .PATH_DATA_SETTING .getKey (), List .of (dataPath .toString ()))
65+ .put (Environment .PATH_HOME_SETTING .getKey (), createTempDir ().toAbsolutePath ())
66+ .build ();
7467
75- // make sure the index tombstones are the same too
76- if (hasMissingCustoms ) {
68+ try (var nodeEnvironment = newNodeEnvironment (settings )) {
69+ final var persistedClusterStateServiceWithFullRegistry = new PersistedClusterStateService (
70+ nodeEnvironment ,
71+ xContentRegistry (),
72+ new ClusterSettings (Settings .EMPTY , ClusterSettings .BUILT_IN_CLUSTER_SETTINGS ),
73+ () -> 0L ,
74+ () -> false
75+ );
76+
77+ // 1. Simulating persisting cluster state by a running node
78+ final long initialTerm = randomNonNegativeLong ();
79+ final Metadata initialMetadata = randomMeta (hasMissingCustoms );
80+ final ClusterState initialState = ClusterState .builder (ClusterState .EMPTY_STATE ).metadata (initialMetadata ).build ();
81+ try (var writer = persistedClusterStateServiceWithFullRegistry .createWriter ()) {
82+ writer .writeFullStateAndCommit (initialTerm , initialState );
83+ }
84+
85+ // 2. Simulating loading the persisted cluster state by the CLI
86+ final var persistedClusterStateServiceForNodeCommand = ElasticsearchNodeCommand .createPersistedClusterStateService (
87+ Settings .EMPTY ,
88+ new Path [] { dataPath }
89+ );
90+ final Tuple <Long , ClusterState > loadedTermAndClusterState = ElasticsearchNodeCommand .loadTermAndClusterState (
91+ persistedClusterStateServiceForNodeCommand ,
92+ TestEnvironment .newEnvironment (settings )
93+ );
94+
95+ assertThat (loadedTermAndClusterState .v1 (), equalTo (initialTerm ));
96+ final var loadedMetadata = loadedTermAndClusterState .v2 ().metadata ();
7797 assertNotNull (loadedMetadata .getProject ().custom (IndexGraveyard .TYPE ));
7898 assertThat (
7999 loadedMetadata .getProject ().custom (IndexGraveyard .TYPE ),
80100 instanceOf (ElasticsearchNodeCommand .UnknownProjectCustom .class )
81101 );
102+ if (hasMissingCustoms ) {
103+ assertThat (
104+ loadedMetadata .getProject ().custom (MissingProjectCustomMetadata .TYPE ),
105+ instanceOf (ElasticsearchNodeCommand .UnknownProjectCustom .class )
106+ );
107+ assertThat (
108+ loadedMetadata .custom (MissingClusterCustomMetadata .TYPE ),
109+ instanceOf (ElasticsearchNodeCommand .UnknownClusterCustom .class )
110+ );
111+ } else {
112+ assertNull (loadedMetadata .getProject ().custom (MissingProjectCustomMetadata .TYPE ));
113+ assertNull (loadedMetadata .custom (MissingClusterCustomMetadata .TYPE ));
114+ }
82115
83- if (preserveUnknownCustoms ) {
84- // check that we reserialize unknown metadata correctly again
85- final Path tempdir = createTempDir ();
86- Metadata .FORMAT .write (loadedMetadata , tempdir );
87- final Metadata reloadedMetadata = Metadata .FORMAT .loadLatestState (logger , xContentRegistry (), tempdir );
88- assertThat (reloadedMetadata .getProject ().indexGraveyard (), equalTo (latestMetadata .getProject ().indexGraveyard ()));
116+ final long newTerm = initialTerm + 1 ;
117+ try (var writer = persistedClusterStateServiceForNodeCommand .createWriter ()) {
118+ writer .writeFullStateAndCommit (newTerm , ClusterState .builder (ClusterState .EMPTY_STATE ).metadata (loadedMetadata ).build ());
119+ }
120+
121+ // 3. Simulate node restart after updating on-disk state with the CLI tool
122+ final var bestOnDiskState = persistedClusterStateServiceWithFullRegistry .loadBestOnDiskState ();
123+ assertThat (bestOnDiskState .currentTerm , equalTo (newTerm ));
124+ final Metadata reloadedMetadata = bestOnDiskState .metadata ;
125+ assertThat (reloadedMetadata .getProject ().indexGraveyard (), equalTo (initialMetadata .getProject ().indexGraveyard ()));
126+ if (hasMissingCustoms ) {
127+ assertThat (
128+ reloadedMetadata .getProject ().custom (MissingProjectCustomMetadata .TYPE ),
129+ equalTo (initialMetadata .getProject ().custom (MissingProjectCustomMetadata .TYPE ))
130+ );
131+ } else {
132+ assertNull (reloadedMetadata .getProject ().custom (MissingProjectCustomMetadata .TYPE ));
89133 }
90- } else {
91- assertThat (loadedMetadata .getProject ().indexGraveyard (), equalTo (latestMetadata .getProject ().indexGraveyard ()));
92134 }
93135 }
94136
95- private Metadata randomMeta () {
137+ private Metadata randomMeta (boolean hasMissingCustoms ) {
96138 Metadata .Builder mdBuilder = Metadata .builder ();
97139 mdBuilder .generateClusterUuidIfNeeded ();
98140 int numDelIndices = randomIntBetween (0 , 5 );
@@ -109,15 +151,86 @@ private Metadata randomMeta() {
109151 }
110152 }
111153 mdBuilder .indexGraveyard (graveyard .build ());
154+ if (hasMissingCustoms ) {
155+ mdBuilder .putCustom (
156+ MissingProjectCustomMetadata .TYPE ,
157+ new MissingProjectCustomMetadata ("test missing project custom metadata" )
158+ );
159+ mdBuilder .putCustom (
160+ MissingClusterCustomMetadata .TYPE ,
161+ new MissingClusterCustomMetadata ("test missing cluster custom metadata" )
162+ );
163+ }
112164 return mdBuilder .build ();
113165 }
114166
115167 @ Override
116168 protected NamedXContentRegistry xContentRegistry () {
117169 return new NamedXContentRegistry (
118- Stream .of (ClusterModule .getNamedXWriteables ().stream (), IndicesModule .getNamedXContents ().stream ())
119- .flatMap (Function .identity ())
120- .toList ()
170+ Stream .of (
171+ ClusterModule .getNamedXWriteables ().stream (),
172+ IndicesModule .getNamedXContents ().stream (),
173+ Stream .of (
174+ new NamedXContentRegistry .Entry (
175+ Metadata .ProjectCustom .class ,
176+ new ParseField (MissingProjectCustomMetadata .TYPE ),
177+ parser -> MissingProjectCustomMetadata .fromXContent (MissingProjectCustomMetadata ::new , parser )
178+ ),
179+ new NamedXContentRegistry .Entry (
180+ Metadata .ClusterCustom .class ,
181+ new ParseField (MissingClusterCustomMetadata .TYPE ),
182+ parser -> MissingClusterCustomMetadata .fromXContent (MissingClusterCustomMetadata ::new , parser )
183+ )
184+ )
185+ ).flatMap (Function .identity ()).toList ()
121186 );
122187 }
188+
189+ private static class MissingProjectCustomMetadata extends TestProjectCustomMetadata {
190+
191+ static final String TYPE = "missing_project_custom_metadata" ;
192+
193+ MissingProjectCustomMetadata (String data ) {
194+ super (data );
195+ }
196+
197+ @ Override
198+ public String getWriteableName () {
199+ return TYPE ;
200+ }
201+
202+ @ Override
203+ public TransportVersion getMinimalSupportedVersion () {
204+ return TransportVersion .current ();
205+ }
206+
207+ @ Override
208+ public EnumSet <Metadata .XContentContext > context () {
209+ return EnumSet .of (Metadata .XContentContext .GATEWAY );
210+ }
211+ }
212+
213+ private static class MissingClusterCustomMetadata extends TestClusterCustomMetadata {
214+
215+ static final String TYPE = "missing_cluster_custom_metadata" ;
216+
217+ MissingClusterCustomMetadata (String data ) {
218+ super (data );
219+ }
220+
221+ @ Override
222+ public String getWriteableName () {
223+ return TYPE ;
224+ }
225+
226+ @ Override
227+ public TransportVersion getMinimalSupportedVersion () {
228+ return TransportVersion .current ();
229+ }
230+
231+ @ Override
232+ public EnumSet <Metadata .XContentContext > context () {
233+ return EnumSet .of (Metadata .XContentContext .GATEWAY );
234+ }
235+ }
123236}
0 commit comments