/*
* Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. Crate 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.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial agreement.
*/
package io.crate;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateListener;
import org.elasticsearch.cluster.ClusterStateUpdateTask;
import org.elasticsearch.cluster.block.ClusterBlocks;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.settings.Settings;
import java.util.concurrent.CompletableFuture;
import static org.elasticsearch.cluster.ClusterState.builder;
@Singleton
public class ClusterIdService extends AbstractLifecycleComponent implements ClusterStateListener {
private static final String CLUSTER_ID_SETTINGS_KEY = "cluster_id";
private final ClusterService clusterService;
private final CompletableFuture<ClusterId> clusterIdFuture = new CompletableFuture<>();
private ClusterId clusterId = null;
@Inject
public ClusterIdService(Settings settings, ClusterService clusterService) {
super(settings);
this.clusterService = clusterService;
}
@Override
public void clusterChanged(ClusterChangedEvent event) {
if (logger.isTraceEnabled()) {
logger.trace("[{}] Receiving new cluster state, reason {}",
clusterService.state().nodes().getLocalNodeId(), event.source());
}
if (event.source().equals("local-gateway-elected-state")) {
// State recovered, read cluster_id
boolean success = applyClusterIdFromSettings();
if (event.localNodeMaster() && !success) {
// None found, generate cluster_id and broadcast it to all nodes
generateClusterId();
saveClusterIdToSettings();
}
}
applyClusterIdFromSettings();
}
/**
* return a CompletableFuture that is available once the clusterId is set
*/
public CompletableFuture<ClusterId> clusterId() {
return clusterIdFuture;
}
private void generateClusterId() {
if (clusterId == null) {
clusterId = new ClusterId();
if (logger.isDebugEnabled()) {
logger.debug("[{}] Generated ClusterId {}",
clusterService.state().nodes().getLocalNodeId(), clusterId.value());
}
clusterIdFuture.complete(clusterId);
}
}
private String readClusterIdFromSettings() {
return clusterService.state().metaData().transientSettings().get(CLUSTER_ID_SETTINGS_KEY);
}
private boolean applyClusterIdFromSettings() {
if (clusterId == null) {
String id = readClusterIdFromSettings();
if (id == null) {
return false;
}
clusterId = new ClusterId(id);
if (logger.isDebugEnabled()) {
logger.debug("[{}] Read ClusterId from settings {}",
clusterService.state().nodes().getLocalNodeId(), clusterId.value());
}
clusterIdFuture.complete(clusterId);
}
return true;
}
private void saveClusterIdToSettings() {
if (logger.isTraceEnabled()) {
logger.trace("Announcing new cluster_id to all nodes");
}
clusterService.submitStateUpdateTask("new_cluster_id", new ClusterStateUpdateTask() {
@Override
public ClusterState execute(ClusterState currentState) throws Exception {
Settings.Builder transientSettings = Settings.builder();
transientSettings.put(currentState.metaData().transientSettings());
transientSettings.put(CLUSTER_ID_SETTINGS_KEY, clusterId.value().toString());
MetaData.Builder metaData = MetaData.builder(currentState.metaData())
.persistentSettings(currentState.metaData().persistentSettings())
.transientSettings(transientSettings.build());
ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks());
boolean updatedReadOnly =
metaData.persistentSettings().getAsBoolean(
MetaData.SETTING_READ_ONLY_SETTING.getKey(), false)
|| metaData.transientSettings().getAsBoolean(
MetaData.SETTING_READ_ONLY_SETTING.getKey(), false);
if (updatedReadOnly) {
blocks.addGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK);
} else {
blocks.removeGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK);
}
return builder(currentState).metaData(metaData).blocks(blocks).build();
}
@Override
public void onFailure(String source, Exception e) {
logger.error("failed to perform [{}]", e, source);
}
});
}
@Override
protected void doStart() {
// add listener here to avoid guice proxy errors if the ClusterService could not be build
clusterService.add(this);
}
@Override
protected void doStop() {
clusterService.remove(this);
}
@Override
protected void doClose() {
}
}