/* * Copyright (c) 2017 Strapdata (http://www.strapdata.com) * Contains some code from Elasticsearch (http://www.elastic.co) * * Licensed 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.elassandra.indices; /** * Post applied cluster state listener. */ import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import org.apache.cassandra.utils.Pair; import org.elassandra.cluster.InternalCassandraClusterService; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterStateListener; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.RoutingTable; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShardRecoveryException; import org.elasticsearch.index.shard.StoreRecoveryService; import org.elasticsearch.indices.IndicesService; /** * Post-applied ClusterState listener to manage * - Start INITIALIZING local shards * - Cassandra 2i creation when associated local shard is started (on coordinator node only). * @author vroyer * */ public class CassandraSecondaryIndicesListener implements ClusterStateListener { ESLogger logger = Loggers.getLogger(CassandraSecondaryIndicesListener.class); private final ClusterService clusterService; private final IndicesService indicesService; private final CopyOnWriteArraySet<Pair<String,MappingMetaData>> updatedMapping = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet<String> initilizingShards = new CopyOnWriteArraySet<>(); public CassandraSecondaryIndicesListener(ClusterService clusterService, IndicesService indicesService) { this.clusterService = clusterService; this.indicesService = indicesService; } // called only by the coordinator of a mapping change on pre-applied phase public void updateMapping(String index, MappingMetaData mapping) { updatedMapping.add(Pair.create(index, mapping)); } public void recoverShard(String index) { initilizingShards.add(index); } // called on post-applied phase (when shards are started on all nodes) @Override public void clusterChanged(ClusterChangedEvent event) { // recover initializing shards DiscoveryNode localNode = event.state().nodes().localNode(); RoutingTable routingTable = event.state().routingTable(); for(String index : initilizingShards) { IndexMetaData indexMetaData = event.state().metaData().index(index); IndexService indexService = this.indicesService.indexService(indexMetaData.getIndex()); IndexShard indexShard = indexService.shardSafe(0); ShardRouting shardRouting = indexShard.routingEntry(); if (indexMetaData.getState() == IndexMetaData.State.OPEN && shardRouting != null) { if (logger.isDebugEnabled()) logger.debug("[{}][{}] state=[{}], recovering", shardRouting.index(), shardRouting.shardId().getId(), shardRouting.state()); // try to recover if index was existing but has no shards. indexShard.recoverFromStore(shardRouting, new StoreRecoveryService.RecoveryListener() { @Override public void onRecoveryDone() { logger.debug("[{}][{}] recovery done, shard state={}", shardRouting.index(),0, indexShard.state()); indexShard.moveToStart(); } @Override public void onIgnoreRecovery(String reason) { logger.warn("[{}][{}] recovery ignored", shardRouting.index(),0); } @Override public void onRecoveryFailed(IndexShardRecoveryException e) { logger.warn("[{}][{}] recovery failed", e, shardRouting.index(),0); } }); } } if (initilizingShards.size() > 0) initilizingShards.clear(); // create cassandra 2i on coordinator node only. for(Pair<String,MappingMetaData> mapping : updatedMapping) { String index = mapping.left; IndexMetaData indexMetaData = event.state().metaData().index(index); if (indexMetaData != null) { try { String clazz = indexMetaData.getSettings().get(IndexMetaData.SETTING_SECONDARY_INDEX_CLASS, event.state().metaData().settings().get(InternalCassandraClusterService.SETTING_CLUSTER_SECONDARY_INDEX_CLASS, InternalCassandraClusterService.defaultSecondaryIndexClass.getName())); logger.debug("Creating secondary indices for table={}.{} with class={}", indexMetaData.keyspace(), mapping.right.type(),clazz); this.clusterService.createSecondaryIndex(indexMetaData.keyspace(), mapping.right, clazz); } catch (IOException e) { logger.error("Failed to create secondary indices for table={}.{}", e, indexMetaData.keyspace(), mapping.right.type()); } } else { logger.warn("Index [{}] not found in new state metadata version={}", index, event.state().metaData().version()); } } if (updatedMapping.size() > 0) updatedMapping.clear(); } }