Show only committed cluster UUID in `GET /` (#114275)

Today we show `Metadata#clusterUUID` in the response to `GET /`
regardless of whether this value is committed or not, which means that
in theory users may see this value change even if nothing is going
wrong. To avoid any doubt about the stability of this cluster UUID, this
commit suppresses the cluster UUID in this API response until it is
committed.
This commit is contained in:
David Turner 2024-10-08 08:47:28 +01:00 committed by GitHub
parent 4ecc5bd53e
commit bb9d612eb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 31 additions and 19 deletions

View File

@ -14,6 +14,7 @@ import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.TransportAction;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.EsExecutors;
@ -48,7 +49,7 @@ public class TransportMainAction extends TransportAction<MainRequest, MainRespon
nodeName,
IndexVersion.current().luceneVersion().toString(),
clusterState.getClusterName(),
clusterState.metadata().clusterUUID(),
clusterState.metadata().clusterUUIDCommitted() ? clusterState.metadata().clusterUUID() : Metadata.UNKNOWN_CLUSTER_UUID,
Build.current()
)
);

View File

@ -9,13 +9,14 @@
package org.elasticsearch.rest.root;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.ActionTestUtils;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlock;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.block.ClusterBlocks;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.RestStatus;
@ -26,7 +27,7 @@ import org.elasticsearch.transport.Transport;
import org.elasticsearch.transport.TransportService;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@ -39,7 +40,7 @@ public class MainActionTests extends ESTestCase {
final ClusterService clusterService = mock(ClusterService.class);
final ClusterName clusterName = new ClusterName("elasticsearch");
final Settings settings = Settings.builder().put("node.name", "my-node").build();
ClusterBlocks blocks;
final ClusterBlocks blocks;
if (randomBoolean()) {
if (randomBoolean()) {
blocks = ClusterBlocks.EMPTY_CLUSTER_BLOCK;
@ -73,7 +74,12 @@ public class MainActionTests extends ESTestCase {
)
.build();
}
ClusterState state = ClusterState.builder(clusterName).blocks(blocks).build();
final Metadata.Builder metadata = new Metadata.Builder();
if (randomBoolean()) {
metadata.clusterUUID(randomUUID());
metadata.clusterUUIDCommitted(randomBoolean());
}
final ClusterState state = ClusterState.builder(clusterName).metadata(metadata).blocks(blocks).build();
when(clusterService.state()).thenReturn(state);
TransportService transportService = new TransportService(
@ -85,21 +91,21 @@ public class MainActionTests extends ESTestCase {
null,
Collections.emptySet()
);
TransportMainAction action = new TransportMainAction(settings, transportService, mock(ActionFilters.class), clusterService);
AtomicReference<MainResponse> responseRef = new AtomicReference<>();
action.doExecute(mock(Task.class), new MainRequest(), new ActionListener<>() {
@Override
public void onResponse(MainResponse mainResponse) {
responseRef.set(mainResponse);
}
final AtomicBoolean listenerCalled = new AtomicBoolean();
new TransportMainAction(settings, transportService, mock(ActionFilters.class), clusterService).doExecute(
mock(Task.class),
new MainRequest(),
ActionTestUtils.assertNoFailureListener(mainResponse -> {
assertNotNull(mainResponse);
assertEquals(
state.metadata().clusterUUIDCommitted() ? state.metadata().clusterUUID() : Metadata.UNKNOWN_CLUSTER_UUID,
mainResponse.getClusterUuid()
);
assertFalse(listenerCalled.getAndSet(true));
})
);
@Override
public void onFailure(Exception e) {
logger.error("unexpected error", e);
}
});
assertNotNull(responseRef.get());
assertTrue(listenerCalled.get());
verify(clusterService, times(1)).state();
}
}

View File

@ -695,6 +695,11 @@ public class Metadata implements Iterable<IndexMetadata>, Diffable<Metadata>, Ch
return this.version;
}
/**
* @return A UUID which identifies this cluster. Nodes record the UUID of the cluster they first join on disk, and will then refuse to
* join clusters with different UUIDs. Note that when the cluster is forming for the first time this value may not yet be committed,
* and therefore it may change. Check {@link #clusterUUIDCommitted()} to verify that the value is committed if needed.
*/
public String clusterUUID() {
return this.clusterUUID;
}