Use non-ILM template setting up watch history template & ILM disabled (#39420)

Backport of #39325

When ILM is disabled and Watcher is setting up the templates and policies for
the watch history indices, it will now use a template that does not have the
`index.lifecycle.name` setting, so that indices are not created with the
setting.

This also adds tests for the behavior, and changes the cluster state used in
these tests to be real instead of mocked.

Resolves #38805
This commit is contained in:
Lee Hinman 2019-02-27 11:11:19 -07:00 committed by GitHub
parent 9c8c158f21
commit ad8228aec9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 706 additions and 27 deletions

View File

@ -17,6 +17,7 @@ public final class WatcherIndexTemplateRegistryField {
// Note: if you change this, also inform the kibana team around the watcher-ui
public static final String INDEX_TEMPLATE_VERSION = "9";
public static final String HISTORY_TEMPLATE_NAME = ".watch-history-" + INDEX_TEMPLATE_VERSION;
public static final String HISTORY_TEMPLATE_NAME_NO_ILM = ".watch-history-no-ilm-" + INDEX_TEMPLATE_VERSION;
public static final String TRIGGERED_TEMPLATE_NAME = ".triggered_watches";
public static final String WATCHES_TEMPLATE_NAME = ".watches";
public static final String[] TEMPLATE_NAMES = new String[] {

View File

@ -0,0 +1,615 @@
{
"index_patterns": [ ".watcher-history-${xpack.watcher.template.version}*" ],
"order": 2147483646,
"settings": {
"index.number_of_shards": 1,
"index.number_of_replicas": 0,
"index.auto_expand_replicas": "0-1",
"index.format": 6
},
"mappings": {
"doc": {
"_meta": {
"watcher-history-version": "${xpack.watcher.template.version}"
},
"dynamic_templates": [
{
"disabled_payload_fields": {
"path_match": "result\\.(input(\\..+)*|(transform(\\..+)*)|(actions\\.transform(\\..+)*))\\.payload",
"match_pattern": "regex",
"mapping": {
"type": "object",
"enabled": false
}
}
},
{
"disabled_search_request_body_fields": {
"path_match": "result\\.(input(\\..+)*|(transform(\\..+)*)|(actions\\.transform(\\..+)*))\\.search\\.request\\.(body|template)",
"match_pattern": "regex",
"mapping": {
"type": "object",
"enabled": false
}
}
},
{
"disabled_exception_fields": {
"path_match": "result\\.(input(\\..+)*|(transform(\\..+)*)|(actions\\.transform(\\..+)*)|actions)\\.error",
"match_pattern": "regex",
"mapping": {
"type": "object",
"enabled": false
}
}
},
{
"disabled_jira_custom_fields": {
"path_match": "result.actions.jira.fields.customfield_*",
"mapping": {
"type": "object",
"enabled": false
}
}
}
],
"dynamic": false,
"properties": {
"watch_id": {
"type": "keyword"
},
"node": {
"type": "keyword"
},
"trigger_event": {
"type": "object",
"dynamic": true,
"properties": {
"type" : {
"type" : "keyword"
},
"triggered_time": {
"type": "date"
},
"manual": {
"type": "object",
"dynamic": true,
"properties": {
"schedule": {
"type": "object",
"dynamic": true,
"properties": {
"scheduled_time": {
"type": "date"
}
}
}
}
},
"schedule": {
"type": "object",
"dynamic": true,
"properties": {
"scheduled_time": {
"type": "date"
}
}
}
}
},
"vars" : {
"type" : "object",
"enabled" : false
},
"input": {
"type": "object",
"enabled": false
},
"condition": {
"type": "object",
"enabled": false
},
"state": {
"type": "keyword"
},
"status": {
"type": "object",
"enabled" : false,
"dynamic" : true
},
"messages": {
"type": "text"
},
"user": {
"type": "text"
},
"exception" : {
"type" : "object",
"enabled" : false
},
"result": {
"type": "object",
"dynamic": true,
"properties": {
"execution_time": {
"type": "date"
},
"execution_duration": {
"type": "long"
},
"input": {
"type": "object",
"dynamic": true,
"properties": {
"type" : {
"type" : "keyword"
},
"status" : {
"type" : "keyword"
},
"payload" : {
"type" : "object",
"enabled" : false
},
"search": {
"type": "object",
"dynamic": true,
"properties": {
"request": {
"type": "object",
"dynamic": true,
"properties": {
"search_type": {
"type": "keyword"
},
"indices": {
"type": "keyword"
},
"types": {
"type": "keyword"
}
}
}
}
},
"http": {
"type": "object",
"dynamic": true,
"properties": {
"request": {
"type": "object",
"dynamic": true,
"properties": {
"path": {
"type": "keyword"
},
"host": {
"type": "keyword"
}
}
}
}
}
}
},
"condition" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"type" : {
"type" : "keyword"
},
"status" : {
"type" : "keyword"
},
"met" : {
"type" : "boolean"
},
"compare" : {
"type" : "object",
"enabled" : false
},
"array_compare" : {
"type" : "object",
"enabled" : false
},
"script" : {
"type" : "object",
"enabled" : false
}
}
},
"transform" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"type" : {
"type" : "keyword"
},
"search" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"request" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"indices" : {
"type" : "keyword"
},
"types" : {
"type" : "keyword"
}
}
}
}
}
}
},
"actions": {
"type": "nested",
"include_in_parent": true,
"dynamic": true,
"properties": {
"id" : {
"type" : "keyword"
},
"type" : {
"type" : "keyword"
},
"status" : {
"type" : "keyword"
},
"reason" : {
"type" : "keyword"
},
"email": {
"type": "object",
"dynamic": true,
"properties": {
"message": {
"type": "object",
"dynamic": true,
"properties": {
"id": {
"type": "keyword"
},
"from": {
"type": "keyword"
},
"reply_to": {
"type": "keyword"
},
"to": {
"type": "keyword"
},
"cc": {
"type": "keyword"
},
"bcc": {
"type": "keyword"
}
}
}
}
},
"webhook": {
"type": "object",
"dynamic": true,
"properties": {
"request": {
"type": "object",
"dynamic": true,
"properties": {
"path": {
"type": "keyword"
},
"host": {
"type": "keyword"
}
}
}
}
},
"index": {
"type": "object",
"dynamic": true,
"properties": {
"response": {
"type": "object",
"dynamic": true,
"properties": {
"index": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"id": {
"type": "keyword"
}
}
}
}
},
"hipchat" : {
"type": "object",
"dynamic": true,
"properties": {
"account": {
"type": "keyword"
},
"sent_messages": {
"type": "nested",
"include_in_parent": true,
"dynamic": true,
"properties": {
"status": {
"type": "keyword"
},
"reason": {
"type": "text"
},
"request" : {
"type" : "object",
"enabled" : false
},
"response" : {
"type" : "object",
"enabled" : false
},
"room" : {
"type": "keyword"
},
"user" : {
"type": "keyword"
},
"message" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"message_format" : {
"type" : "keyword"
},
"color" : {
"type" : "keyword"
},
"notify" : {
"type" : "boolean"
},
"message" : {
"type" : "text"
},
"from" : {
"type" : "text"
}
}
}
}
}
}
},
"jira" : {
"type": "object",
"dynamic": true,
"properties": {
"account": {
"type": "keyword"
},
"reason": {
"type": "text"
},
"request" : {
"type" : "object",
"enabled" : false
},
"response" : {
"type" : "object",
"enabled" : false
},
"fields": {
"type": "object",
"dynamic": true,
"properties": {
"summary": {
"type": "text"
},
"description": {
"type": "text"
},
"labels" : {
"type": "text"
},
"project" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"key" : {
"type" : "keyword"
},
"id" : {
"type" : "keyword"
}
}
},
"issuetype" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"name" : {
"type": "keyword"
},
"id" : {
"type" : "keyword"
}
}
}
}
},
"result": {
"type": "object",
"dynamic": true,
"properties" : {
"id" : {
"type" : "keyword"
},
"key" : {
"type" : "keyword"
},
"self" : {
"type" : "keyword"
}
}
}
}
},
"slack" : {
"type": "object",
"dynamic": true,
"properties": {
"account": {
"type": "keyword"
},
"sent_messages": {
"type": "nested",
"include_in_parent": true,
"dynamic": true,
"properties": {
"status": {
"type": "keyword"
},
"reason": {
"type": "text"
},
"request" : {
"type" : "object",
"enabled" : false
},
"response" : {
"type" : "object",
"enabled" : false
},
"to" : {
"type": "keyword"
},
"message" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"from" : {
"type" : "text"
},
"icon" : {
"type" : "keyword"
},
"text" : {
"type" : "text"
},
"attachments" : {
"type" : "nested",
"include_in_parent": true,
"dynamic" : true,
"properties" : {
"color" : {
"type" : "keyword"
},
"fields" : {
"properties" : {
"value" : {
"type" : "text"
}
}
}
}
}
}
}
}
}
}
},
"pagerduty" : {
"type": "object",
"dynamic": true,
"properties": {
"account": {
"type": "keyword"
},
"sent_event": {
"type": "nested",
"include_in_parent": true,
"dynamic": true,
"properties": {
"reason": {
"type": "text"
},
"request" : {
"type" : "object",
"enabled" : false
},
"response" : {
"type" : "object",
"enabled" : false
},
"event" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"type" : {
"type" : "keyword"
},
"client" : {
"type" : "text"
},
"client_url" : {
"type" : "keyword"
},
"account" : {
"type" : "keyword"
},
"attach_payload" : {
"type" : "boolean"
},
"incident_key" : {
"type" : "keyword"
},
"description" : {
"type" : "text"
},
"context" : {
"type" : "nested",
"include_in_parent": true,
"dynamic" : true,
"properties" : {
"type" : {
"type" : "keyword"
},
"href" : {
"type" : "keyword"
},
"src" : {
"type" : "keyword"
},
"alt" : {
"type" : "text"
}
}
}
}
}
}
}
}
}
}
}
}
},
"metadata": {
"type": "object",
"dynamic": true
}
}
}
}
}

View File

@ -23,6 +23,7 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.XPackClient;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata;
import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicy;
import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicyUtils;
@ -46,11 +47,16 @@ public class WatcherIndexTemplateRegistry implements ClusterStateListener {
public static final TemplateConfig TEMPLATE_CONFIG_TRIGGERED_WATCHES = new TemplateConfig(
WatcherIndexTemplateRegistryField.TRIGGERED_TEMPLATE_NAME, "triggered-watches");
public static final TemplateConfig TEMPLATE_CONFIG_WATCH_HISTORY = new TemplateConfig(
WatcherIndexTemplateRegistryField.HISTORY_TEMPLATE_NAME, "watch-history");
WatcherIndexTemplateRegistryField.HISTORY_TEMPLATE_NAME, "watch-history");
public static final TemplateConfig TEMPLATE_CONFIG_WATCH_HISTORY_NO_ILM = new TemplateConfig(
WatcherIndexTemplateRegistryField.HISTORY_TEMPLATE_NAME_NO_ILM, "watch-history-no-ilm");
public static final TemplateConfig TEMPLATE_CONFIG_WATCHES = new TemplateConfig(
WatcherIndexTemplateRegistryField.WATCHES_TEMPLATE_NAME, "watches");
public static final TemplateConfig[] TEMPLATE_CONFIGS = new TemplateConfig[]{
TEMPLATE_CONFIG_TRIGGERED_WATCHES, TEMPLATE_CONFIG_WATCH_HISTORY, TEMPLATE_CONFIG_WATCHES
TEMPLATE_CONFIG_TRIGGERED_WATCHES, TEMPLATE_CONFIG_WATCH_HISTORY, TEMPLATE_CONFIG_WATCHES
};
public static final TemplateConfig[] TEMPLATE_CONFIGS_NO_ILM = new TemplateConfig[]{
TEMPLATE_CONFIG_TRIGGERED_WATCHES, TEMPLATE_CONFIG_WATCH_HISTORY_NO_ILM, TEMPLATE_CONFIG_WATCHES
};
public static final PolicyConfig POLICY_WATCH_HISTORY = new PolicyConfig("watch-history-ilm-policy", "/watch-history-ilm-policy.json");
@ -59,7 +65,6 @@ public class WatcherIndexTemplateRegistry implements ClusterStateListener {
private final Client client;
private final ThreadPool threadPool;
private final TemplateConfig[] indexTemplates;
private final NamedXContentRegistry xContentRegistry;
private final ConcurrentMap<String, AtomicBoolean> templateCreationsInProgress = new ConcurrentHashMap<>();
private final AtomicBoolean historyPolicyCreationInProgress = new AtomicBoolean();
@ -68,7 +73,6 @@ public class WatcherIndexTemplateRegistry implements ClusterStateListener {
NamedXContentRegistry xContentRegistry) {
this.client = client;
this.threadPool = threadPool;
this.indexTemplates = TEMPLATE_CONFIGS;
this.xContentRegistry = xContentRegistry;
clusterService.addListener(this);
}
@ -100,6 +104,8 @@ public class WatcherIndexTemplateRegistry implements ClusterStateListener {
}
private void addTemplatesIfMissing(ClusterState state) {
boolean ilmSupported = XPackSettings.INDEX_LIFECYCLE_ENABLED.get(state.metaData().settings());
final TemplateConfig[] indexTemplates = ilmSupported ? TEMPLATE_CONFIGS : TEMPLATE_CONFIGS_NO_ILM;
for (TemplateConfig template : indexTemplates) {
final String templateName = template.getTemplateName();
final AtomicBoolean creationCheck = templateCreationsInProgress.computeIfAbsent(templateName, key -> new AtomicBoolean(false));
@ -147,7 +153,8 @@ public class WatcherIndexTemplateRegistry implements ClusterStateListener {
}
private void addIndexLifecyclePolicyIfMissing(ClusterState state) {
if (historyPolicyCreationInProgress.compareAndSet(false, true)) {
boolean ilmSupported = XPackSettings.INDEX_LIFECYCLE_ENABLED.get(state.metaData().settings());
if (ilmSupported && historyPolicyCreationInProgress.compareAndSet(false, true)) {
final LifecyclePolicy policyOnDisk = loadWatcherHistoryPolicy();
Optional<IndexLifecycleMetadata> maybeMeta = Optional.ofNullable(state.metaData().custom(IndexLifecycleMetadata.TYPE));

View File

@ -31,14 +31,16 @@ import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.indexlifecycle.DeleteAction;
import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata;
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleAction;
import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicy;
import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicyMetadata;
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleType;
import org.elasticsearch.xpack.core.indexlifecycle.OperationMode;
import org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType;
import org.elasticsearch.xpack.core.indexlifecycle.action.PutLifecycleAction;
import org.elasticsearch.xpack.core.watcher.support.WatcherIndexTemplateRegistryField;
@ -52,15 +54,18 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.elasticsearch.mock.orig.Mockito.verify;
import static org.elasticsearch.mock.orig.Mockito.when;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verifyZeroInteractions;
@ -110,9 +115,35 @@ public class WatcherIndexTemplateRegistryTests extends ESTestCase {
// now delete one template from the cluster state and lets retry
ClusterChangedEvent newEvent = createClusterChangedEvent(Arrays.asList(WatcherIndexTemplateRegistryField.HISTORY_TEMPLATE_NAME,
WatcherIndexTemplateRegistryField.TRIGGERED_TEMPLATE_NAME), nodes);
WatcherIndexTemplateRegistryField.TRIGGERED_TEMPLATE_NAME), nodes);
registry.clusterChanged(newEvent);
verify(client.admin().indices(), times(4)).putTemplate(argumentCaptor.capture(), anyObject());
ArgumentCaptor<PutIndexTemplateRequest> captor = ArgumentCaptor.forClass(PutIndexTemplateRequest.class);
verify(client.admin().indices(), times(4)).putTemplate(captor.capture(), anyObject());
PutIndexTemplateRequest req = captor.getAllValues().stream()
.filter(r -> r.name().equals(WatcherIndexTemplateRegistryField.HISTORY_TEMPLATE_NAME))
.findFirst()
.orElseThrow(() -> new AssertionError("expected the watch history template to be put"));
assertThat(req.settings().get("index.lifecycle.name"), equalTo("watch-history-ilm-policy"));
}
public void testThatNonExistingTemplatesAreAddedEvenWithILMDisabled() {
DiscoveryNode node = new DiscoveryNode("node", ESTestCase.buildNewFakeTransportAddress(), Version.CURRENT);
DiscoveryNodes nodes = DiscoveryNodes.builder().localNodeId("node").masterNodeId("node").add(node).build();
ClusterChangedEvent event = createClusterChangedEvent(Settings.builder()
.put(XPackSettings.INDEX_LIFECYCLE_ENABLED.getKey(), false).build(),
Collections.emptyList(), Collections.emptyMap(), nodes);
registry.clusterChanged(event);
ArgumentCaptor<PutIndexTemplateRequest> argumentCaptor = ArgumentCaptor.forClass(PutIndexTemplateRequest.class);
verify(client.admin().indices(), times(3)).putTemplate(argumentCaptor.capture(), anyObject());
// now delete one template from the cluster state and lets retry
ClusterChangedEvent newEvent = createClusterChangedEvent(Arrays.asList(WatcherIndexTemplateRegistryField.HISTORY_TEMPLATE_NAME,
WatcherIndexTemplateRegistryField.TRIGGERED_TEMPLATE_NAME), nodes);
registry.clusterChanged(newEvent);
ArgumentCaptor<PutIndexTemplateRequest> captor = ArgumentCaptor.forClass(PutIndexTemplateRequest.class);
verify(client.admin().indices(), times(4)).putTemplate(captor.capture(), anyObject());
captor.getAllValues().forEach(req -> assertNull(req.settings().get("index.lifecycle.name")));
}
public void testThatNonExistingPoliciesAreAddedImmediately() {
@ -136,6 +167,17 @@ public class WatcherIndexTemplateRegistryTests extends ESTestCase {
verify(client, times(0)).execute(eq(PutLifecycleAction.INSTANCE), anyObject(), anyObject());
}
public void testNoPolicyButILMDisabled() {
DiscoveryNode node = new DiscoveryNode("node", ESTestCase.buildNewFakeTransportAddress(), Version.CURRENT);
DiscoveryNodes nodes = DiscoveryNodes.builder().localNodeId("node").masterNodeId("node").add(node).build();
ClusterChangedEvent event = createClusterChangedEvent(Settings.builder()
.put(XPackSettings.INDEX_LIFECYCLE_ENABLED.getKey(), false).build(),
Collections.emptyList(), Collections.emptyMap(), nodes);
registry.clusterChanged(event);
verify(client, times(0)).execute(eq(PutLifecycleAction.INSTANCE), anyObject(), anyObject());
}
public void testPolicyAlreadyExistsButDiffers() throws IOException {
DiscoveryNode node = new DiscoveryNode("node", ESTestCase.buildNewFakeTransportAddress(), Version.CURRENT);
DiscoveryNodes nodes = DiscoveryNodes.builder().localNodeId("node").masterNodeId("node").add(node).build();
@ -206,31 +248,45 @@ public class WatcherIndexTemplateRegistryTests extends ESTestCase {
return createClusterChangedEvent(existingTemplateNames, Collections.emptyMap(), nodes);
}
private ClusterChangedEvent createClusterChangedEvent(List<String> existingTemplateNames,
Map<String, LifecyclePolicy> existingPolicies,
DiscoveryNodes nodes) {
ClusterChangedEvent event = mock(ClusterChangedEvent.class);
when(event.localNodeMaster()).thenReturn(nodes.isLocalNodeElectedMaster());
ClusterState cs = mock(ClusterState.class);
ClusterBlocks clusterBlocks = mock(ClusterBlocks.class);
when(clusterBlocks.hasGlobalBlock(eq(GatewayService.STATE_NOT_RECOVERED_BLOCK))).thenReturn(false);
when(cs.blocks()).thenReturn(clusterBlocks);
when(event.state()).thenReturn(cs);
when(cs.getNodes()).thenReturn(nodes);
MetaData metaData = mock(MetaData.class);
private ClusterState createClusterState(Settings nodeSettings,
List<String> existingTemplateNames,
Map<String, LifecyclePolicy> existingPolicies,
DiscoveryNodes nodes) {
ImmutableOpenMap.Builder<String, IndexTemplateMetaData> indexTemplates = ImmutableOpenMap.builder();
for (String name : existingTemplateNames) {
indexTemplates.put(name, mock(IndexTemplateMetaData.class));
}
when(metaData.getTemplates()).thenReturn(indexTemplates.build());
Map<String, LifecyclePolicyMetadata> existingILMMeta = existingPolicies.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> new LifecyclePolicyMetadata(e.getValue(), Collections.emptyMap(), 1, 1)));
IndexLifecycleMetadata ilmMeta = new IndexLifecycleMetadata(existingILMMeta, OperationMode.RUNNING);
IndexLifecycleMetadata ilmMeta = mock(IndexLifecycleMetadata.class);
when(ilmMeta.getPolicies()).thenReturn(existingPolicies);
when(metaData.custom(anyObject())).thenReturn(ilmMeta);
when(cs.metaData()).thenReturn(metaData);
return ClusterState.builder(new ClusterName("test"))
.metaData(MetaData.builder()
.templates(indexTemplates.build())
.transientSettings(nodeSettings)
.putCustom(IndexLifecycleMetadata.TYPE, ilmMeta)
.build())
.blocks(new ClusterBlocks.Builder().build())
.nodes(nodes)
.build();
}
private ClusterChangedEvent createClusterChangedEvent(List<String> existingTemplateNames,
Map<String, LifecyclePolicy> existingPolicies,
DiscoveryNodes nodes) {
return createClusterChangedEvent(Settings.EMPTY, existingTemplateNames, existingPolicies, nodes);
}
private ClusterChangedEvent createClusterChangedEvent(Settings nodeSettings,
List<String> existingTemplateNames,
Map<String, LifecyclePolicy> existingPolicies,
DiscoveryNodes nodes) {
ClusterState cs = createClusterState(nodeSettings, existingTemplateNames, existingPolicies, nodes);
ClusterChangedEvent realEvent = new ClusterChangedEvent("created-from-test", cs,
ClusterState.builder(new ClusterName("test")).build());
ClusterChangedEvent event = spy(realEvent);
when(event.localNodeMaster()).thenReturn(nodes.isLocalNodeElectedMaster());
return event;
}