/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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.elasticsearch.discovery.zen; import org.elasticsearch.Version; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateTaskExecutor; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.routing.allocation.AllocationService; import org.elasticsearch.test.ESTestCase; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import static org.hamcrest.CoreMatchers.equalTo; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; public class NodeRemovalClusterStateTaskExecutorTests extends ESTestCase { public void testRemovingNonExistentNodes() throws Exception { final ZenDiscovery.NodeRemovalClusterStateTaskExecutor executor = new ZenDiscovery.NodeRemovalClusterStateTaskExecutor(null, null, null, logger); final DiscoveryNodes.Builder builder = DiscoveryNodes.builder(); final int nodes = randomIntBetween(2, 16); for (int i = 0; i < nodes; i++) { builder.add(node(i)); } final ClusterState clusterState = ClusterState.builder(new ClusterName("test")).nodes(builder).build(); final DiscoveryNodes.Builder removeBuilder = DiscoveryNodes.builder(); for (int i = nodes; i < nodes + randomIntBetween(1, 16); i++) { removeBuilder.add(node(i)); } final List<ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task> tasks = StreamSupport .stream(removeBuilder.build().spliterator(), false) .map(node -> new ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task(node, randomBoolean() ? "left" : "failed")) .collect(Collectors.toList()); final ClusterStateTaskExecutor.ClusterTasksResult<ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task> result = executor.execute(clusterState, tasks); assertThat(result.resultingState, equalTo(clusterState)); } public void testNotEnoughMasterNodesAfterRemove() throws Exception { final ElectMasterService electMasterService = mock(ElectMasterService.class); when(electMasterService.hasEnoughMasterNodes(any(Iterable.class))).thenReturn(false); final AllocationService allocationService = mock(AllocationService.class); final AtomicBoolean rejoinCalled = new AtomicBoolean(); final Consumer<String> submitRejoin = source -> rejoinCalled.set(true); final AtomicReference<ClusterState> remainingNodesClusterState = new AtomicReference<>(); final ZenDiscovery.NodeRemovalClusterStateTaskExecutor executor = new ZenDiscovery.NodeRemovalClusterStateTaskExecutor(allocationService, electMasterService, submitRejoin, logger) { @Override ClusterState remainingNodesClusterState(ClusterState currentState, DiscoveryNodes.Builder remainingNodesBuilder) { remainingNodesClusterState.set(super.remainingNodesClusterState(currentState, remainingNodesBuilder)); return remainingNodesClusterState.get(); } }; final DiscoveryNodes.Builder builder = DiscoveryNodes.builder(); final int nodes = randomIntBetween(2, 16); final List<ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task> tasks = new ArrayList<>(); // to ensure there is at least one removal boolean first = true; for (int i = 0; i < nodes; i++) { final DiscoveryNode node = node(i); builder.add(node); if (first || randomBoolean()) { tasks.add(new ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task(node, randomBoolean() ? "left" : "failed")); } first = false; } final ClusterState clusterState = ClusterState.builder(new ClusterName("test")).nodes(builder).build(); final ClusterStateTaskExecutor.ClusterTasksResult<ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task> result = executor.execute(clusterState, tasks); verify(electMasterService).hasEnoughMasterNodes(eq(remainingNodesClusterState.get().nodes())); verify(electMasterService).countMasterNodes(eq(remainingNodesClusterState.get().nodes())); verify(electMasterService).minimumMasterNodes(); verifyNoMoreInteractions(electMasterService); // ensure that we did not reroute verifyNoMoreInteractions(allocationService); assertTrue(rejoinCalled.get()); assertThat(result.resultingState, equalTo(clusterState)); for (final ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task task : tasks) { assertNotNull(result.resultingState.nodes().get(task.node().getId())); } } public void testRerouteAfterRemovingNodes() throws Exception { final ElectMasterService electMasterService = mock(ElectMasterService.class); when(electMasterService.hasEnoughMasterNodes(any(Iterable.class))).thenReturn(true); final AllocationService allocationService = mock(AllocationService.class); when(allocationService.deassociateDeadNodes(any(ClusterState.class), eq(true), any(String.class))) .thenAnswer(im -> im.getArguments()[0]); final Consumer<String> submitRejoin = source -> fail("rejoin should not be invoked"); final AtomicReference<ClusterState> remainingNodesClusterState = new AtomicReference<>(); final ZenDiscovery.NodeRemovalClusterStateTaskExecutor executor = new ZenDiscovery.NodeRemovalClusterStateTaskExecutor(allocationService, electMasterService, submitRejoin, logger) { @Override ClusterState remainingNodesClusterState(ClusterState currentState, DiscoveryNodes.Builder remainingNodesBuilder) { remainingNodesClusterState.set(super.remainingNodesClusterState(currentState, remainingNodesBuilder)); return remainingNodesClusterState.get(); } }; final DiscoveryNodes.Builder builder = DiscoveryNodes.builder(); final int nodes = randomIntBetween(2, 16); final List<ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task> tasks = new ArrayList<>(); // to ensure that there is at least one removal boolean first = true; for (int i = 0; i < nodes; i++) { final DiscoveryNode node = node(i); builder.add(node); if (first || randomBoolean()) { tasks.add(new ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task(node, randomBoolean() ? "left" : "failed")); } first = false; } final ClusterState clusterState = ClusterState.builder(new ClusterName("test")).nodes(builder).build(); final ClusterStateTaskExecutor.ClusterTasksResult<ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task> result = executor.execute(clusterState, tasks); verify(electMasterService).hasEnoughMasterNodes(eq(remainingNodesClusterState.get().nodes())); verifyNoMoreInteractions(electMasterService); verify(allocationService).deassociateDeadNodes(eq(remainingNodesClusterState.get()), eq(true), any(String.class)); for (final ZenDiscovery.NodeRemovalClusterStateTaskExecutor.Task task : tasks) { assertNull(result.resultingState.nodes().get(task.node().getId())); } } private DiscoveryNode node(final int id) { return new DiscoveryNode(Integer.toString(id), buildNewFakeTransportAddress(), Version.CURRENT); } }