SOLR-14616: Remove CDCR

This commit is contained in:
Ishan Chattopadhyaya 2020-08-29 02:13:13 +05:30
parent da8ea70682
commit d84977eb5c
59 changed files with 11 additions and 10415 deletions
solr
CHANGES.txt
core/src
solr-ref-guide/src
test-framework/src/java/org/apache/solr

View File

@ -48,6 +48,8 @@ Other Changes
----------------------
* SOLR-14656: Autoscaling framework removed (Ishan Chattopadhyaya, noble, Ilan Ginzburg)
* SOLR-14616: CDCR support removed (Ishan Chattopadhyaya)
* LUCENE-9391: Upgrade HPPC to 0.8.2. (Haoyu Zhai)
* SOLR-10288: Remove non-minified JavaScript from the webapp. (Erik Hatcher, marcussorealheis)

View File

@ -58,7 +58,6 @@ import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.update.CdcrUpdateLog;
import org.apache.solr.update.CommitUpdateCommand;
import org.apache.solr.update.PeerSyncWithLeader;
import org.apache.solr.update.UpdateLog;
@ -241,12 +240,6 @@ public class RecoveryStrategy implements Runnable, Closeable {
ModifiableSolrParams solrParams = new ModifiableSolrParams();
solrParams.set(ReplicationHandler.LEADER_URL, leaderUrl);
solrParams.set(ReplicationHandler.SKIP_COMMIT_ON_LEADER_VERSION_ZERO, replicaType == Replica.Type.TLOG);
// always download the tlogs from the leader when running with cdcr enabled. We need to have all the tlogs
// to ensure leader failover doesn't cause missing docs on the target
if (core.getUpdateHandler().getUpdateLog() != null
&& core.getUpdateHandler().getUpdateLog() instanceof CdcrUpdateLog) {
solrParams.set(ReplicationHandler.TLOG_FILES, true);
}
if (isClosed()) return; // we check closed on return
boolean success = replicationHandler.doFetch(solrParams, false).getSuccessful();

View File

@ -1,71 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import org.apache.solr.core.SolrCore;
import org.apache.solr.update.CdcrUpdateLog;
/**
* This manager is responsible in enabling or disabling the buffering of the update logs. Currently, buffer
* is always activated for non-leader nodes. For leader nodes, it is enabled only if the user explicitly
* enabled it with the action {@link org.apache.solr.handler.CdcrParams.CdcrAction#ENABLEBUFFER}.
*/
class CdcrBufferManager implements CdcrStateManager.CdcrStateObserver {
private CdcrLeaderStateManager leaderStateManager;
private CdcrBufferStateManager bufferStateManager;
private final SolrCore core;
CdcrBufferManager(SolrCore core) {
this.core = core;
}
void setLeaderStateManager(final CdcrLeaderStateManager leaderStateManager) {
this.leaderStateManager = leaderStateManager;
this.leaderStateManager.register(this);
}
void setBufferStateManager(final CdcrBufferStateManager bufferStateManager) {
this.bufferStateManager = bufferStateManager;
this.bufferStateManager.register(this);
}
/**
* This method is synchronised as it can both be called by the leaderStateManager and the bufferStateManager.
*/
@Override
public synchronized void stateUpdate() {
CdcrUpdateLog ulog = (CdcrUpdateLog) core.getUpdateHandler().getUpdateLog();
// If I am not the leader, I should always buffer my updates
if (!leaderStateManager.amILeader()) {
ulog.enableBuffer();
return;
}
// If I am the leader, I should buffer my updates only if buffer is enabled
else if (bufferStateManager.getState().equals(CdcrParams.BufferState.ENABLED)) {
ulog.enableBuffer();
return;
}
// otherwise, disable the buffer
ulog.disableBuffer();
}
}

View File

@ -1,178 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.SolrCore;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;
import java.nio.charset.Charset;
/**
* Manage the state of the update log buffer. It is responsible of synchronising the state
* through Zookeeper. The state of the buffer is stored in the zk node defined by {@link #getZnodePath()}.
* @deprecated since 8.6
*/
@Deprecated(since = "8.6")
class CdcrBufferStateManager extends CdcrStateManager {
private CdcrParams.BufferState state = DEFAULT_STATE;
private BufferStateWatcher wrappedWatcher;
private Watcher watcher;
private SolrCore core;
static CdcrParams.BufferState DEFAULT_STATE = CdcrParams.BufferState.ENABLED;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
CdcrBufferStateManager(final SolrCore core, SolrParams bufferConfiguration) {
this.core = core;
// Ensure that the state znode exists
this.createStateNode();
// set default state
if (bufferConfiguration != null) {
byte[] defaultState = bufferConfiguration.get(
CdcrParams.DEFAULT_STATE_PARAM, DEFAULT_STATE.toLower()).getBytes(Charset.forName("UTF-8"));
state = CdcrParams.BufferState.get(defaultState);
}
this.setState(state); // notify observers
// Startup and register the watcher at startup
try {
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
watcher = this.initWatcher(zkClient);
this.setState(CdcrParams.BufferState.get(zkClient.getData(this.getZnodePath(), watcher, null, true)));
} catch (KeeperException | InterruptedException e) {
log.warn("Failed fetching initial state", e);
}
}
/**
* SolrZkClient does not guarantee that a watch object will only be triggered once for a given notification
* if we does not wrap the watcher - see SOLR-6621.
*/
private Watcher initWatcher(SolrZkClient zkClient) {
wrappedWatcher = new BufferStateWatcher();
return zkClient.wrapWatcher(wrappedWatcher);
}
private String getZnodeBase() {
return "/collections/" + core.getCoreDescriptor().getCloudDescriptor().getCollectionName() + "/cdcr/state";
}
private String getZnodePath() {
return getZnodeBase() + "/buffer";
}
void setState(CdcrParams.BufferState state) {
if (this.state != state) {
this.state = state;
this.callback(); // notify the observers of a state change
}
}
CdcrParams.BufferState getState() {
return state;
}
/**
* Synchronise the state to Zookeeper. This method must be called only by the handler receiving the
* action.
*/
void synchronize() {
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
try {
zkClient.setData(this.getZnodePath(), this.getState().getBytes(), true);
// check if nobody changed it in the meantime, and set a new watcher
this.setState(CdcrParams.BufferState.get(zkClient.getData(this.getZnodePath(), watcher, null, true)));
} catch (KeeperException | InterruptedException e) {
log.warn("Failed synchronising new state", e);
}
}
private void createStateNode() {
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
try {
if (!zkClient.exists(this.getZnodePath(), true)) {
if (!zkClient.exists(this.getZnodeBase(), true)) {
zkClient.makePath(this.getZnodeBase(), null, CreateMode.PERSISTENT, null, false, true); // Should be a no-op if node exists
}
zkClient.create(this.getZnodePath(), DEFAULT_STATE.getBytes(), CreateMode.PERSISTENT, true);
if (log.isInfoEnabled()) {
log.info("Created znode {}", this.getZnodePath());
}
}
} catch (KeeperException.NodeExistsException ne) {
// Someone got in first and created the node.
} catch (KeeperException | InterruptedException e) {
log.warn("Failed to create CDCR buffer state node", e);
}
}
void shutdown() {
if (wrappedWatcher != null) {
wrappedWatcher.cancel(); // cancel the watcher to avoid spurious warn messages during shutdown
}
}
private class BufferStateWatcher implements Watcher {
private boolean isCancelled = false;
/**
* Cancel the watcher to avoid spurious warn messages during shutdown.
*/
void cancel() {
isCancelled = true;
}
@Override
public void process(WatchedEvent event) {
if (isCancelled) return; // if the watcher is cancelled, do nothing.
String collectionName = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
String shard = core.getCoreDescriptor().getCloudDescriptor().getShardId();
log.info("The CDCR buffer state has changed: {} @ {}:{}", event, collectionName, shard);
// session events are not change events, and do not remove the watcher
if (Event.EventType.None.equals(event.getType())) {
return;
}
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
try {
CdcrParams.BufferState state = CdcrParams.BufferState.get(zkClient.getData(CdcrBufferStateManager.this.getZnodePath(), watcher, null, true));
log.info("Received new CDCR buffer state from watcher: {} @ {}:{}", state, collectionName, shard);
CdcrBufferStateManager.this.setState(state);
} catch (KeeperException | InterruptedException e) {
log.warn("Failed synchronising new state @ {}:{}", collectionName, shard, e);
}
}
}
}

View File

@ -1,162 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import java.lang.invoke.MethodHandles;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.core.SolrCore;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Manage the leader state of the CDCR nodes.
* </p>
* <p>
* It takes care of notifying the {@link CdcrReplicatorManager} in case
* of a leader state change.
* </p>
* @deprecated since 8.6
*/
@Deprecated(since = "8.6")
class CdcrLeaderStateManager extends CdcrStateManager {
private boolean amILeader = false;
private LeaderStateWatcher wrappedWatcher;
private Watcher watcher;
private SolrCore core;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
CdcrLeaderStateManager(final SolrCore core) {
this.core = core;
// Fetch leader state and register the watcher at startup
try {
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
ClusterState clusterState = core.getCoreContainer().getZkController().getClusterState();
watcher = this.initWatcher(zkClient);
// if the node does not exist, it means that the leader was not yet registered. This can happen
// when the cluster is starting up. The core is not yet fully loaded, and the leader election process
// is waiting for it.
if (this.isLeaderRegistered(zkClient, clusterState)) {
this.checkIfIAmLeader();
}
} catch (KeeperException | InterruptedException e) {
log.warn("Failed fetching initial leader state and setting watch", e);
}
}
/**
* Checks if the leader is registered. If it is not registered, we are probably at the
* initialisation phase of the cluster. In this case, we must attach a watcher to
* be notified when the leader is registered.
*/
private boolean isLeaderRegistered(SolrZkClient zkClient, ClusterState clusterState)
throws KeeperException, InterruptedException {
// First check if the znode exists, and register the watcher at the same time
return zkClient.exists(this.getZnodePath(), watcher, true) != null;
}
/**
* SolrZkClient does not guarantee that a watch object will only be triggered once for a given notification
* if we does not wrap the watcher - see SOLR-6621.
*/
private Watcher initWatcher(SolrZkClient zkClient) {
wrappedWatcher = new LeaderStateWatcher();
return zkClient.wrapWatcher(wrappedWatcher);
}
private void checkIfIAmLeader() throws KeeperException, InterruptedException {
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
ZkNodeProps props = ZkNodeProps.load(zkClient.getData(CdcrLeaderStateManager.this.getZnodePath(), null, null, true));
if (props != null) {
CdcrLeaderStateManager.this.setAmILeader(props.get("core").equals(core.getName()));
}
}
private String getZnodePath() {
String myShardId = core.getCoreDescriptor().getCloudDescriptor().getShardId();
String myCollection = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
return "/collections/" + myCollection + "/leaders/" + myShardId + "/leader";
}
void setAmILeader(boolean amILeader) {
if (this.amILeader != amILeader) {
this.amILeader = amILeader;
this.callback(); // notify the observers of a state change
}
}
boolean amILeader() {
return amILeader;
}
void shutdown() {
if (wrappedWatcher != null) {
wrappedWatcher.cancel(); // cancel the watcher to avoid spurious warn messages during shutdown
}
}
private class LeaderStateWatcher implements Watcher {
private boolean isCancelled = false;
/**
* Cancel the watcher to avoid spurious warn messages during shutdown.
*/
void cancel() {
isCancelled = true;
}
@Override
public void process(WatchedEvent event) {
if (isCancelled) return; // if the watcher is cancelled, do nothing.
String collectionName = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
String shard = core.getCoreDescriptor().getCloudDescriptor().getShardId();
log.debug("The leader state has changed: {} @ {}:{}", event, collectionName, shard);
// session events are not change events, and do not remove the watcher
if (Event.EventType.None.equals(event.getType())) {
return;
}
try {
log.info("Received new leader state @ {}:{}", collectionName, shard);
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
ClusterState clusterState = core.getCoreContainer().getZkController().getClusterState();
if (CdcrLeaderStateManager.this.isLeaderRegistered(zkClient, clusterState)) {
CdcrLeaderStateManager.this.checkIfIAmLeader();
}
} catch (KeeperException | InterruptedException e) {
log.warn("Failed updating leader state and setting watch @ {}: {}", collectionName, shard, e);
}
}
}
}

View File

@ -1,256 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import java.nio.charset.Charset;
import java.util.Locale;
public class CdcrParams {
/**
* The definition of a replica configuration *
*/
public static final String REPLICA_PARAM = "replica";
/**
* The source collection of a replica *
*/
public static final String SOURCE_COLLECTION_PARAM = "source";
/**
* The target collection of a replica *
*/
public static final String TARGET_COLLECTION_PARAM = "target";
/**
* The Zookeeper host of the target cluster hosting the replica *
*/
public static final String ZK_HOST_PARAM = "zkHost";
/**
* The definition of the {@link org.apache.solr.handler.CdcrReplicatorScheduler} configuration *
*/
public static final String REPLICATOR_PARAM = "replicator";
/**
* The thread pool size of the replicator *
*/
public static final String THREAD_POOL_SIZE_PARAM = "threadPoolSize";
/**
* The time schedule (in ms) of the replicator *
*/
public static final String SCHEDULE_PARAM = "schedule";
/**
* The batch size of the replicator *
*/
public static final String BATCH_SIZE_PARAM = "batchSize";
/**
* The definition of the {@link org.apache.solr.handler.CdcrUpdateLogSynchronizer} configuration *
*/
public static final String UPDATE_LOG_SYNCHRONIZER_PARAM = "updateLogSynchronizer";
/**
* The definition of the {@link org.apache.solr.handler.CdcrBufferManager} configuration *
*/
public static final String BUFFER_PARAM = "buffer";
/**
* The default state at startup of the buffer *
*/
public static final String DEFAULT_STATE_PARAM = "defaultState";
/**
* The latest update checkpoint on a target cluster *
*/
public final static String CHECKPOINT = "checkpoint";
/**
* The last processed version on a source cluster *
*/
public final static String LAST_PROCESSED_VERSION = "lastProcessedVersion";
/**
* A list of replica queues on a source cluster *
*/
public final static String QUEUES = "queues";
/**
* The size of a replica queue on a source cluster *
*/
public final static String QUEUE_SIZE = "queueSize";
/**
* The timestamp of the last processed operation in a replica queue *
*/
public final static String LAST_TIMESTAMP = "lastTimestamp";
/**
* A list of qps statistics per collection *
*/
public final static String OPERATIONS_PER_SECOND = "operationsPerSecond";
/**
* Overall counter *
*/
public final static String COUNTER_ALL = "all";
/**
* Counter for Adds *
*/
public final static String COUNTER_ADDS = "adds";
/**
* Counter for Deletes *
*/
public final static String COUNTER_DELETES = "deletes";
/**
* Counter for Bootstrap operations *
*/
public final static String COUNTER_BOOTSTRAP = "bootstraps";
/**
* A list of errors per target collection *
*/
public final static String ERRORS = "errors";
/**
* Counter for consecutive errors encountered by a replicator thread *
*/
public final static String CONSECUTIVE_ERRORS = "consecutiveErrors";
/**
* A list of the last errors encountered by a replicator thread *
*/
public final static String LAST = "last";
/**
* Total size of transaction logs *
*/
public final static String TLOG_TOTAL_SIZE = "tlogTotalSize";
/**
* Total count of transaction logs *
*/
public final static String TLOG_TOTAL_COUNT = "tlogTotalCount";
/**
* The state of the update log synchronizer *
*/
public final static String UPDATE_LOG_SYNCHRONIZER = "updateLogSynchronizer";
/**
* The actions supported by the CDCR API
*/
public enum CdcrAction {
START,
STOP,
STATUS,
COLLECTIONCHECKPOINT,
SHARDCHECKPOINT,
ENABLEBUFFER,
DISABLEBUFFER,
LASTPROCESSEDVERSION,
QUEUES,
OPS,
ERRORS,
BOOTSTRAP,
BOOTSTRAP_STATUS,
CANCEL_BOOTSTRAP;
public static CdcrAction get(String p) {
if (p != null) {
try {
return CdcrAction.valueOf(p.toUpperCase(Locale.ROOT));
} catch (Exception e) {
}
}
return null;
}
public String toLower() {
return toString().toLowerCase(Locale.ROOT);
}
}
/**
* The possible states of the CDCR process
*/
public enum ProcessState {
STARTED,
STOPPED;
public static ProcessState get(byte[] state) {
if (state != null) {
try {
return ProcessState.valueOf(new String(state, Charset.forName("UTF-8")).toUpperCase(Locale.ROOT));
} catch (Exception e) {
}
}
return null;
}
public String toLower() {
return toString().toLowerCase(Locale.ROOT);
}
public byte[] getBytes() {
return toLower().getBytes(Charset.forName("UTF-8"));
}
public static String getParam() {
return "process";
}
}
/**
* The possible states of the CDCR buffer
*/
public enum BufferState {
ENABLED,
DISABLED;
public static BufferState get(byte[] state) {
if (state != null) {
try {
return BufferState.valueOf(new String(state, Charset.forName("UTF-8")).toUpperCase(Locale.ROOT));
} catch (Exception e) {
}
}
return null;
}
public String toLower() {
return toString().toLowerCase(Locale.ROOT);
}
public byte[] getBytes() {
return toLower().getBytes(Charset.forName("UTF-8"));
}
public static String getParam() {
return "buffer";
}
}
}

View File

@ -1,178 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import java.lang.invoke.MethodHandles;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.core.SolrCore;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Manage the life-cycle state of the CDCR process. It is responsible of synchronising the state
* through Zookeeper. The state of the CDCR process is stored in the zk node defined by {@link #getZnodePath()}.
* </p>
* <p>
* It takes care of notifying the {@link CdcrReplicatorManager} in case
* of a process state change.
* </p>
* @deprecated since 8.6
*/
@Deprecated(since = "8.6")
class CdcrProcessStateManager extends CdcrStateManager {
private CdcrParams.ProcessState state = DEFAULT_STATE;
private ProcessStateWatcher wrappedWatcher;
private Watcher watcher;
private SolrCore core;
/**
* The default state must be STOPPED. See comments in
* {@link #setState(org.apache.solr.handler.CdcrParams.ProcessState)}.
*/
static CdcrParams.ProcessState DEFAULT_STATE = CdcrParams.ProcessState.STOPPED;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
CdcrProcessStateManager(final SolrCore core) {
this.core = core;
// Ensure that the status znode exists
this.createStateNode();
// Register the watcher at startup
try {
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
watcher = this.initWatcher(zkClient);
this.setState(CdcrParams.ProcessState.get(zkClient.getData(this.getZnodePath(), watcher, null, true)));
} catch (KeeperException | InterruptedException e) {
log.warn("Failed fetching initial state", e);
}
}
/**
* SolrZkClient does not guarantee that a watch object will only be triggered once for a given notification
* if we does not wrap the watcher - see SOLR-6621.
*/
private Watcher initWatcher(SolrZkClient zkClient) {
wrappedWatcher = new ProcessStateWatcher();
return zkClient.wrapWatcher(wrappedWatcher);
}
private String getZnodeBase() {
return "/collections/" + core.getCoreDescriptor().getCloudDescriptor().getCollectionName() + "/cdcr/state";
}
private String getZnodePath() {
return getZnodeBase() + "/process";
}
void setState(CdcrParams.ProcessState state) {
if (this.state != state) {
this.state = state;
this.callback(); // notify the observers of a state change
}
}
CdcrParams.ProcessState getState() {
return state;
}
/**
* Synchronise the state to Zookeeper. This method must be called only by the handler receiving the
* action.
*/
void synchronize() {
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
try {
zkClient.setData(this.getZnodePath(), this.getState().getBytes(), true);
// check if nobody changed it in the meantime, and set a new watcher
this.setState(CdcrParams.ProcessState.get(zkClient.getData(this.getZnodePath(), watcher, null, true)));
} catch (KeeperException | InterruptedException e) {
log.warn("Failed synchronising new state", e);
}
}
private void createStateNode() {
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
try {
if (!zkClient.exists(this.getZnodePath(), true)) {
if (!zkClient.exists(this.getZnodeBase(), true)) { // Should be a no-op if the node exists
zkClient.makePath(this.getZnodeBase(), null, CreateMode.PERSISTENT, null, false, true);
}
zkClient.create(this.getZnodePath(), DEFAULT_STATE.getBytes(), CreateMode.PERSISTENT, true);
if (log.isInfoEnabled()) {
log.info("Created znode {}", this.getZnodePath());
}
}
} catch (KeeperException.NodeExistsException ne) {
// Someone got in first and created the node.
} catch (KeeperException | InterruptedException e) {
log.warn("Failed to create CDCR process state node", e);
}
}
void shutdown() {
if (wrappedWatcher != null) {
wrappedWatcher.cancel(); // cancel the watcher to avoid spurious warn messages during shutdown
}
}
private class ProcessStateWatcher implements Watcher {
private boolean isCancelled = false;
/**
* Cancel the watcher to avoid spurious warn messages during shutdown.
*/
void cancel() {
isCancelled = true;
}
@Override
public void process(WatchedEvent event) {
if (isCancelled) return; // if the watcher is cancelled, do nothing.
String collectionName = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
String shard = core.getCoreDescriptor().getCloudDescriptor().getShardId();
log.info("The CDCR process state has changed: {} @ {}:{}", event, collectionName, shard);
// session events are not change events, and do not remove the watcher
if (Event.EventType.None.equals(event.getType())) {
return;
}
SolrZkClient zkClient = core.getCoreContainer().getZkController().getZkClient();
try {
CdcrParams.ProcessState state = CdcrParams.ProcessState.get(zkClient.getData(CdcrProcessStateManager.this.getZnodePath(), watcher, null, true));
log.info("Received new CDCR process state from watcher: {} @ {}:{}", state, collectionName, shard);
CdcrProcessStateManager.this.setState(state);
} catch (KeeperException | InterruptedException e) {
log.warn("Failed synchronising new state @ {}: {}", collectionName, shard, e);
}
}
}
}

View File

@ -1,258 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.charset.Charset;
import java.util.List;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.update.CdcrUpdateLog;
import org.apache.solr.update.UpdateLog;
import org.apache.solr.update.processor.CdcrUpdateProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.common.params.CommonParams.VERSION_FIELD;
/**
* The replication logic. Given a {@link org.apache.solr.handler.CdcrReplicatorState}, it reads all the new entries
* in the update log and forward them to the target cluster. If an error occurs, the replication is stopped and
* will be tried again later.
* @deprecated since 8.6
*/
@Deprecated(since = "8.6")
public class CdcrReplicator implements Runnable {
private final CdcrReplicatorState state;
private final int batchSize;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public CdcrReplicator(CdcrReplicatorState state, int batchSize) {
this.state = state;
this.batchSize = batchSize;
}
@Override
public void run() {
CdcrUpdateLog.CdcrLogReader logReader = state.getLogReader();
CdcrUpdateLog.CdcrLogReader subReader = null;
if (logReader == null) {
log.warn("Log reader for target {} is not initialised, it will be ignored.", state.getTargetCollection());
return;
}
try {
// create update request
UpdateRequest req = new UpdateRequest();
// Add the param to indicate the {@link CdcrUpdateProcessor} to keep the provided version number
req.setParam(CdcrUpdateProcessor.CDCR_UPDATE, "");
// Start the benchmark timer
state.getBenchmarkTimer().start();
long counter = 0;
subReader = logReader.getSubReader();
for (int i = 0; i < batchSize; i++) {
Object o = subReader.next();
if (o == null) break; // we have reached the end of the update logs, we should close the batch
if (isTargetCluster(o)) {
continue;
}
if (isDelete(o)) {
/*
* Deletes are sent one at a time.
*/
// First send out current batch of SolrInputDocument, the non-deletes.
List<SolrInputDocument> docs = req.getDocuments();
if (docs != null && docs.size() > 0) {
subReader.resetToLastPosition(); // Push back the delete for now.
this.sendRequest(req); // Send the batch update request
logReader.forwardSeek(subReader); // Advance the main reader to just before the delete.
o = subReader.next(); // Read the delete again
counter += docs.size();
req.clear();
}
// Process Delete
this.processUpdate(o, req);
this.sendRequest(req);
logReader.forwardSeek(subReader);
counter++;
req.clear();
} else {
this.processUpdate(o, req);
}
}
//Send the final batch out.
List<SolrInputDocument> docs = req.getDocuments();
if ((docs != null && docs.size() > 0)) {
this.sendRequest(req);
counter += docs.size();
}
// we might have read a single commit operation and reached the end of the update logs
logReader.forwardSeek(subReader);
if (log.isInfoEnabled()) {
log.info("Forwarded {} updates to target {}", counter, state.getTargetCollection());
}
} catch (Exception e) {
// report error and update error stats
this.handleException(e);
} finally {
// stop the benchmark timer
state.getBenchmarkTimer().stop();
// ensure that the subreader is closed and the associated pointer is removed
if (subReader != null) subReader.close();
}
}
private void sendRequest(UpdateRequest req) throws IOException, SolrServerException, CdcrReplicatorException {
UpdateResponse rsp = req.process(state.getClient());
if (rsp.getStatus() != 0) {
throw new CdcrReplicatorException(req, rsp);
}
state.resetConsecutiveErrors();
}
/** check whether the update read from TLog is received from source
* or received via solr client
*/
private boolean isTargetCluster(Object o) {
@SuppressWarnings({"rawtypes"})
List entry = (List) o;
int operationAndFlags = (Integer) entry.get(0);
int oper = operationAndFlags & UpdateLog.OPERATION_MASK;
Boolean isTarget = false;
if (oper == UpdateLog.DELETE_BY_QUERY || oper == UpdateLog.DELETE) {
if (entry.size() == 4) { //back-combat - skip for previous versions
isTarget = (Boolean) entry.get(entry.size() - 1);
}
} else if (oper == UpdateLog.UPDATE_INPLACE) {
if (entry.size() == 6) { //back-combat - skip for previous versions
isTarget = (Boolean) entry.get(entry.size() - 2);
}
} else if (oper == UpdateLog.ADD) {
if (entry.size() == 4) { //back-combat - skip for previous versions
isTarget = (Boolean) entry.get(entry.size() - 2);
}
}
return isTarget;
}
private boolean isDelete(Object o) {
@SuppressWarnings({"rawtypes"})
List entry = (List) o;
int operationAndFlags = (Integer) entry.get(0);
int oper = operationAndFlags & UpdateLog.OPERATION_MASK;
return oper == UpdateLog.DELETE_BY_QUERY || oper == UpdateLog.DELETE;
}
private void handleException(Exception e) {
if (e instanceof CdcrReplicatorException) {
UpdateRequest req = ((CdcrReplicatorException) e).req;
UpdateResponse rsp = ((CdcrReplicatorException) e).rsp;
log.warn("Failed to forward update request {} to target: {}. Got response {}", req, state.getTargetCollection(), rsp);
state.reportError(CdcrReplicatorState.ErrorType.BAD_REQUEST);
} else if (e instanceof CloudSolrClient.RouteException) {
log.warn("Failed to forward update request to target: {}", state.getTargetCollection(), e);
state.reportError(CdcrReplicatorState.ErrorType.BAD_REQUEST);
} else {
log.warn("Failed to forward update request to target: {}", state.getTargetCollection(), e);
state.reportError(CdcrReplicatorState.ErrorType.INTERNAL);
}
}
private UpdateRequest processUpdate(Object o, UpdateRequest req) {
// should currently be a List<Oper,Ver,Doc/Id>
@SuppressWarnings({"rawtypes"})
List entry = (List) o;
int operationAndFlags = (Integer) entry.get(0);
int oper = operationAndFlags & UpdateLog.OPERATION_MASK;
long version = (Long) entry.get(1);
// record the operation in the benchmark timer
state.getBenchmarkTimer().incrementCounter(oper);
switch (oper) {
case UpdateLog.ADD: {
// the version is already attached to the document
SolrInputDocument sdoc = (SolrInputDocument) entry.get(entry.size() - 1);
req.add(sdoc);
return req;
}
case UpdateLog.DELETE: {
byte[] idBytes = (byte[]) entry.get(2);
req.deleteById(new String(idBytes, Charset.forName("UTF-8")));
req.setParam(VERSION_FIELD, Long.toString(version));
return req;
}
case UpdateLog.DELETE_BY_QUERY: {
String query = (String) entry.get(2);
req.deleteByQuery(query);
req.setParam(VERSION_FIELD, Long.toString(version));
return req;
}
case UpdateLog.COMMIT: {
return null;
}
default:
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown Operation! " + oper);
}
}
/**
* Exception to catch update request issues with the target cluster.
*/
public static class CdcrReplicatorException extends Exception {
private final UpdateRequest req;
private final UpdateResponse rsp;
public CdcrReplicatorException(UpdateRequest req, UpdateResponse rsp) {
this.req = req;
this.rsp = rsp;
}
}
}

View File

@ -1,441 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import java.io.Closeable;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.HttpClient;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.CloudSolrClient.Builder;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.ZkCoreNodeProps;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.IOUtils;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.apache.solr.common.util.TimeSource;
import org.apache.solr.core.SolrCore;
import org.apache.solr.update.CdcrUpdateLog;
import org.apache.solr.util.TimeOut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.handler.admin.CoreAdminHandler.RESPONSE_STATUS;
@Deprecated(since = "8.6")
class CdcrReplicatorManager implements CdcrStateManager.CdcrStateObserver {
private static final int MAX_BOOTSTRAP_ATTEMPTS = 5;
private static final int BOOTSTRAP_RETRY_DELAY_MS = 2000;
// 6 hours is hopefully long enough for most indexes
private static final long BOOTSTRAP_TIMEOUT_SECONDS = 6L * 3600L * 3600L;
private List<CdcrReplicatorState> replicatorStates;
private final CdcrReplicatorScheduler scheduler;
private CdcrProcessStateManager processStateManager;
private CdcrLeaderStateManager leaderStateManager;
private SolrCore core;
private String path;
private ExecutorService bootstrapExecutor;
private volatile BootstrapStatusRunnable bootstrapStatusRunnable;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
CdcrReplicatorManager(final SolrCore core, String path,
SolrParams replicatorConfiguration,
Map<String, List<SolrParams>> replicasConfiguration) {
this.core = core;
this.path = path;
// create states
replicatorStates = new ArrayList<>();
String myCollection = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
List<SolrParams> targets = replicasConfiguration.get(myCollection);
if (targets != null) {
for (SolrParams params : targets) {
String zkHost = params.get(CdcrParams.ZK_HOST_PARAM);
String targetCollection = params.get(CdcrParams.TARGET_COLLECTION_PARAM);
CloudSolrClient client = new Builder(Collections.singletonList(zkHost), Optional.empty())
.withSocketTimeout(30000).withConnectionTimeout(15000)
.sendUpdatesOnlyToShardLeaders()
.build();
client.setDefaultCollection(targetCollection);
replicatorStates.add(new CdcrReplicatorState(targetCollection, zkHost, client));
}
}
this.scheduler = new CdcrReplicatorScheduler(this, replicatorConfiguration);
}
void setProcessStateManager(final CdcrProcessStateManager processStateManager) {
this.processStateManager = processStateManager;
this.processStateManager.register(this);
}
void setLeaderStateManager(final CdcrLeaderStateManager leaderStateManager) {
this.leaderStateManager = leaderStateManager;
this.leaderStateManager.register(this);
}
/**
* <p>
* Inform the replicator manager of a change of state, and tell him to update its own state.
* </p>
* <p>
* If we are the leader and the process state is STARTED, we need to initialise the log readers and start the
* scheduled thread poll.
* Otherwise, if the process state is STOPPED or if we are not the leader, we need to close the log readers and stop
* the thread pool.
* </p>
* <p>
* This method is synchronised as it can both be called by the leaderStateManager and the processStateManager.
* </p>
*/
@Override
public synchronized void stateUpdate() {
if (leaderStateManager.amILeader() && processStateManager.getState().equals(CdcrParams.ProcessState.STARTED)) {
if (replicatorStates.size() > 0) {
this.bootstrapExecutor = ExecutorUtil.newMDCAwareFixedThreadPool(replicatorStates.size(),
new SolrNamedThreadFactory("cdcr-bootstrap-status"));
}
this.initLogReaders();
this.scheduler.start();
return;
}
this.scheduler.shutdown();
if (bootstrapExecutor != null) {
IOUtils.closeQuietly(bootstrapStatusRunnable);
ExecutorUtil.shutdownAndAwaitTermination(bootstrapExecutor);
}
this.closeLogReaders();
@SuppressWarnings({"rawtypes"})
Callable callable = core.getSolrCoreState().getCdcrBootstrapCallable();
if (callable != null) {
CdcrRequestHandler.BootstrapCallable bootstrapCallable = (CdcrRequestHandler.BootstrapCallable) callable;
IOUtils.closeQuietly(bootstrapCallable);
}
}
List<CdcrReplicatorState> getReplicatorStates() {
return replicatorStates;
}
private void initLogReaders() {
String collectionName = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
String shard = core.getCoreDescriptor().getCloudDescriptor().getShardId();
CdcrUpdateLog ulog = (CdcrUpdateLog) core.getUpdateHandler().getUpdateLog();
for (CdcrReplicatorState state : replicatorStates) {
state.closeLogReader();
try {
long checkpoint = this.getCheckpoint(state);
if (log.isInfoEnabled()) {
log.info("Create new update log reader for target {} with checkpoint {} @ {}:{}", state.getTargetCollection(),
checkpoint, collectionName, shard);
}
CdcrUpdateLog.CdcrLogReader reader = ulog.newLogReader();
boolean seek = reader.seek(checkpoint);
state.init(reader);
if (!seek) {
// targetVersion is lower than the oldest known entry.
// In this scenario, it probably means that there is a gap in the updates log.
// the best we can do here is to bootstrap the target leader by replicating the full index
final String targetCollection = state.getTargetCollection();
state.setBootstrapInProgress(true);
log.info("Attempting to bootstrap target collection: {}, shard: {}", targetCollection, shard);
bootstrapStatusRunnable = new BootstrapStatusRunnable(core, state);
log.info("Submitting bootstrap task to executor");
try {
bootstrapExecutor.submit(bootstrapStatusRunnable);
} catch (Exception e) {
log.error("Unable to submit bootstrap call to executor", e);
}
}
} catch (IOException | SolrServerException | SolrException e) {
log.warn("Unable to instantiate the log reader for target collection {}", state.getTargetCollection(), e);
} catch (InterruptedException e) {
log.warn("Thread interrupted while instantiate the log reader for target collection {}", state.getTargetCollection(), e);
Thread.currentThread().interrupt();
}
}
}
private long getCheckpoint(CdcrReplicatorState state) throws IOException, SolrServerException {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CommonParams.ACTION, CdcrParams.CdcrAction.COLLECTIONCHECKPOINT.toString());
@SuppressWarnings({"rawtypes"})
SolrRequest request = new QueryRequest(params);
request.setPath(path);
@SuppressWarnings({"rawtypes"})
NamedList response = state.getClient().request(request);
return (Long) response.get(CdcrParams.CHECKPOINT);
}
void closeLogReaders() {
for (CdcrReplicatorState state : replicatorStates) {
state.closeLogReader();
}
}
/**
* Shutdown all the {@link org.apache.solr.handler.CdcrReplicatorState} by closing their
* {@link org.apache.solr.client.solrj.impl.CloudSolrClient} and
* {@link org.apache.solr.update.CdcrUpdateLog.CdcrLogReader}.
*/
void shutdown() {
this.scheduler.shutdown();
if (bootstrapExecutor != null) {
IOUtils.closeQuietly(bootstrapStatusRunnable);
ExecutorUtil.shutdownAndAwaitTermination(bootstrapExecutor);
}
for (CdcrReplicatorState state : replicatorStates) {
state.shutdown();
}
replicatorStates.clear();
}
private class BootstrapStatusRunnable implements Runnable, Closeable {
private final CdcrReplicatorState state;
private final String targetCollection;
private final String shard;
private final String collectionName;
private final CdcrUpdateLog ulog;
private final String myCoreUrl;
private volatile boolean closed = false;
BootstrapStatusRunnable(SolrCore core, CdcrReplicatorState state) {
this.collectionName = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
this.shard = core.getCoreDescriptor().getCloudDescriptor().getShardId();
this.ulog = (CdcrUpdateLog) core.getUpdateHandler().getUpdateLog();
this.state = state;
this.targetCollection = state.getTargetCollection();
String baseUrl = core.getCoreContainer().getZkController().getBaseUrl();
this.myCoreUrl = ZkCoreNodeProps.getCoreUrl(baseUrl, core.getName());
}
@Override
public void close() throws IOException {
closed = true;
try {
Replica leader = state.getClient().getZkStateReader().getLeaderRetry(targetCollection, shard, 30000); // assume same shard exists on target
String leaderCoreUrl = leader.getCoreUrl();
HttpClient httpClient = state.getClient().getLbClient().getHttpClient();
try (HttpSolrClient client = new HttpSolrClient.Builder(leaderCoreUrl).withHttpClient(httpClient).build()) {
sendCdcrCommand(client, CdcrParams.CdcrAction.CANCEL_BOOTSTRAP);
} catch (SolrServerException e) {
log.error("Error sending cancel bootstrap message to target collection: {} shard: {} leader: {}",
targetCollection, shard, leaderCoreUrl);
}
} catch (InterruptedException e) {
log.error("Interrupted while closing BootstrapStatusRunnable", e);
Thread.currentThread().interrupt();
}
}
@Override
public void run() {
int retries = 1;
boolean success = false;
try {
while (!closed && sendBootstrapCommand() != BootstrapStatus.SUBMITTED) {
Thread.sleep(BOOTSTRAP_RETRY_DELAY_MS);
}
TimeOut timeOut = new TimeOut(BOOTSTRAP_TIMEOUT_SECONDS, TimeUnit.SECONDS, TimeSource.NANO_TIME);
while (!timeOut.hasTimedOut()) {
if (closed) {
log.warn("Cancelling waiting for bootstrap on target: {} shard: {} to complete", targetCollection, shard);
state.setBootstrapInProgress(false);
break;
}
BootstrapStatus status = getBoostrapStatus();
if (status == BootstrapStatus.RUNNING) {
try {
log.info("CDCR bootstrap running for {} seconds, sleeping for {} ms",
BOOTSTRAP_TIMEOUT_SECONDS - timeOut.timeLeft(TimeUnit.SECONDS), BOOTSTRAP_RETRY_DELAY_MS);
timeOut.sleep(BOOTSTRAP_RETRY_DELAY_MS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} else if (status == BootstrapStatus.COMPLETED) {
log.info("CDCR bootstrap successful in {} seconds", BOOTSTRAP_TIMEOUT_SECONDS - timeOut.timeLeft(TimeUnit.SECONDS));
long checkpoint = CdcrReplicatorManager.this.getCheckpoint(state);
if (log.isInfoEnabled()) {
log.info("Create new update log reader for target {} with checkpoint {} @ {}:{}", state.getTargetCollection(),
checkpoint, collectionName, shard);
}
CdcrUpdateLog.CdcrLogReader reader1 = ulog.newLogReader();
reader1.seek(checkpoint);
success = true;
break;
} else if (status == BootstrapStatus.FAILED) {
log.warn("CDCR bootstrap failed in {} seconds", BOOTSTRAP_TIMEOUT_SECONDS - timeOut.timeLeft(TimeUnit.SECONDS));
// let's retry a fixed number of times before giving up
if (retries >= MAX_BOOTSTRAP_ATTEMPTS) {
log.error("Unable to bootstrap the target collection: {}, shard: {} even after {} retries", targetCollection, shard, retries);
break;
} else {
log.info("Retry: {} - Attempting to bootstrap target collection: {} shard: {}", retries, targetCollection, shard);
while (!closed && sendBootstrapCommand() != BootstrapStatus.SUBMITTED) {
Thread.sleep(BOOTSTRAP_RETRY_DELAY_MS);
}
timeOut = new TimeOut(BOOTSTRAP_TIMEOUT_SECONDS, TimeUnit.SECONDS, TimeSource.NANO_TIME); // reset the timer
retries++;
}
} else if (status == BootstrapStatus.NOTFOUND || status == BootstrapStatus.CANCELLED) {
if (log.isInfoEnabled()) {
log.info("CDCR bootstrap {} in {} seconds"
, (status == BootstrapStatus.NOTFOUND ? "not found" : "cancelled")
, BOOTSTRAP_TIMEOUT_SECONDS - timeOut.timeLeft(TimeUnit.SECONDS));
}
// the leader of the target shard may have changed and therefore there is no record of the
// bootstrap process so we must retry the operation
while (!closed && sendBootstrapCommand() != BootstrapStatus.SUBMITTED) {
Thread.sleep(BOOTSTRAP_RETRY_DELAY_MS);
}
retries = 1;
timeOut = new TimeOut(6L * 3600L * 3600L, TimeUnit.SECONDS, TimeSource.NANO_TIME); // reset the timer
} else if (status == BootstrapStatus.UNKNOWN || status == BootstrapStatus.SUBMITTED) {
if (log.isInfoEnabled()) {
log.info("CDCR bootstrap is {} {}", (status == BootstrapStatus.UNKNOWN ? "unknown" : "submitted"),
BOOTSTRAP_TIMEOUT_SECONDS - timeOut.timeLeft(TimeUnit.SECONDS));
}
// we were not able to query the status on the remote end
// so just sleep for a bit and try again
timeOut.sleep(BOOTSTRAP_RETRY_DELAY_MS);
}
}
} catch (InterruptedException e) {
log.info("Bootstrap thread interrupted");
state.reportError(CdcrReplicatorState.ErrorType.INTERNAL);
Thread.currentThread().interrupt();
} catch (IOException | SolrServerException | SolrException e) {
log.error("Unable to bootstrap the target collection {} shard: {}", targetCollection, shard, e);
state.reportError(CdcrReplicatorState.ErrorType.BAD_REQUEST);
} finally {
if (success) {
log.info("Bootstrap successful, giving the go-ahead to replicator");
state.setBootstrapInProgress(false);
}
}
}
private BootstrapStatus sendBootstrapCommand() throws InterruptedException {
Replica leader = state.getClient().getZkStateReader().getLeaderRetry(targetCollection, shard, 30000); // assume same shard exists on target
String leaderCoreUrl = leader.getCoreUrl();
HttpClient httpClient = state.getClient().getLbClient().getHttpClient();
try (HttpSolrClient client = new HttpSolrClient.Builder(leaderCoreUrl).withHttpClient(httpClient).build()) {
log.info("Attempting to bootstrap target collection: {} shard: {} leader: {}", targetCollection, shard, leaderCoreUrl);
try {
@SuppressWarnings({"rawtypes"})
NamedList response = sendCdcrCommand(client, CdcrParams.CdcrAction.BOOTSTRAP, ReplicationHandler.LEADER_URL, myCoreUrl);
log.debug("CDCR Bootstrap response: {}", response);
String status = response.get(RESPONSE_STATUS).toString();
return BootstrapStatus.valueOf(status.toUpperCase(Locale.ROOT));
} catch (Exception e) {
log.error("Exception submitting bootstrap request", e);
return BootstrapStatus.UNKNOWN;
}
} catch (IOException e) {
log.error("There shouldn't be an IOException while closing but there was!", e);
}
return BootstrapStatus.UNKNOWN;
}
private BootstrapStatus getBoostrapStatus() throws InterruptedException {
try {
Replica leader = state.getClient().getZkStateReader().getLeaderRetry(targetCollection, shard, 30000); // assume same shard exists on target
String leaderCoreUrl = leader.getCoreUrl();
HttpClient httpClient = state.getClient().getLbClient().getHttpClient();
try (HttpSolrClient client = new HttpSolrClient.Builder(leaderCoreUrl).withHttpClient(httpClient).build()) {
@SuppressWarnings({"rawtypes"})
NamedList response = sendCdcrCommand(client, CdcrParams.CdcrAction.BOOTSTRAP_STATUS);
String status = (String) response.get(RESPONSE_STATUS);
BootstrapStatus bootstrapStatus = BootstrapStatus.valueOf(status.toUpperCase(Locale.ROOT));
if (bootstrapStatus == BootstrapStatus.RUNNING) {
return BootstrapStatus.RUNNING;
} else if (bootstrapStatus == BootstrapStatus.COMPLETED) {
return BootstrapStatus.COMPLETED;
} else if (bootstrapStatus == BootstrapStatus.FAILED) {
return BootstrapStatus.FAILED;
} else if (bootstrapStatus == BootstrapStatus.NOTFOUND) {
log.warn("Bootstrap process was not found on target collection: {} shard: {}, leader: {}", targetCollection, shard, leaderCoreUrl);
return BootstrapStatus.NOTFOUND;
} else if (bootstrapStatus == BootstrapStatus.CANCELLED) {
return BootstrapStatus.CANCELLED;
} else {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Unknown status: " + status + " returned by BOOTSTRAP_STATUS command");
}
}
} catch (Exception e) {
log.error("Exception during bootstrap status request", e);
return BootstrapStatus.UNKNOWN;
}
}
}
@SuppressWarnings({"rawtypes"})
private NamedList sendCdcrCommand(SolrClient client, CdcrParams.CdcrAction action, String... params) throws SolrServerException, IOException {
ModifiableSolrParams solrParams = new ModifiableSolrParams();
solrParams.set(CommonParams.QT, "/cdcr");
solrParams.set(CommonParams.ACTION, action.toString());
for (int i = 0; i < params.length - 1; i+=2) {
solrParams.set(params[i], params[i + 1]);
}
SolrRequest request = new QueryRequest(solrParams);
return client.request(request);
}
private enum BootstrapStatus {
SUBMITTED,
RUNNING,
COMPLETED,
FAILED,
NOTFOUND,
CANCELLED,
UNKNOWN
}
}

View File

@ -1,116 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.*;
/**
* Schedule the execution of the {@link org.apache.solr.handler.CdcrReplicator} threads at
* regular time interval. It relies on a queue of {@link org.apache.solr.handler.CdcrReplicatorState} in
* order to avoid that one {@link org.apache.solr.handler.CdcrReplicatorState} is used by two threads at the same
* time.
*/
class CdcrReplicatorScheduler {
private boolean isStarted = false;
private ScheduledExecutorService scheduler;
private ExecutorService replicatorsPool;
private final CdcrReplicatorManager replicatorManager;
private final ConcurrentLinkedQueue<CdcrReplicatorState> statesQueue;
private int poolSize = DEFAULT_POOL_SIZE;
private int timeSchedule = DEFAULT_TIME_SCHEDULE;
private int batchSize = DEFAULT_BATCH_SIZE;
private static final int DEFAULT_POOL_SIZE = 2;
private static final int DEFAULT_TIME_SCHEDULE = 10;
private static final int DEFAULT_BATCH_SIZE = 128;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
CdcrReplicatorScheduler(final CdcrReplicatorManager replicatorStatesManager, final SolrParams replicatorConfiguration) {
this.replicatorManager = replicatorStatesManager;
this.statesQueue = new ConcurrentLinkedQueue<>(replicatorManager.getReplicatorStates());
if (replicatorConfiguration != null) {
poolSize = replicatorConfiguration.getInt(CdcrParams.THREAD_POOL_SIZE_PARAM, DEFAULT_POOL_SIZE);
timeSchedule = replicatorConfiguration.getInt(CdcrParams.SCHEDULE_PARAM, DEFAULT_TIME_SCHEDULE);
batchSize = replicatorConfiguration.getInt(CdcrParams.BATCH_SIZE_PARAM, DEFAULT_BATCH_SIZE);
}
}
void start() {
if (!isStarted) {
scheduler = Executors.newSingleThreadScheduledExecutor(new SolrNamedThreadFactory("cdcr-scheduler"));
replicatorsPool = ExecutorUtil.newMDCAwareFixedThreadPool(poolSize, new SolrNamedThreadFactory("cdcr-replicator"));
// the scheduler thread is executed every second and submits one replication task
// per available state in the queue
scheduler.scheduleWithFixedDelay(() -> {
int nCandidates = statesQueue.size();
for (int i = 0; i < nCandidates; i++) {
// a thread that poll one state from the queue, execute the replication task, and push back
// the state in the queue when the task is completed
replicatorsPool.execute(() -> {
CdcrReplicatorState state = statesQueue.poll();
assert state != null; // Should never happen
try {
if (!state.isBootstrapInProgress()) {
new CdcrReplicator(state, batchSize).run();
} else {
if (log.isDebugEnabled()) {
log.debug("Replicator state is bootstrapping, skipping replication for target collection {}", state.getTargetCollection());
}
}
} finally {
statesQueue.offer(state);
}
});
}
}, 0, timeSchedule, TimeUnit.MILLISECONDS);
isStarted = true;
}
}
void shutdown() {
if (isStarted) {
// interrupts are often dangerous in Lucene / Solr code, but the
// test for this will leak threads without
replicatorsPool.shutdown();
try {
replicatorsPool.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("Thread interrupted while waiting for CDCR replicator threadpool close.");
Thread.currentThread().interrupt();
} finally {
scheduler.shutdownNow();
isStarted = false;
}
}
}
}

View File

@ -1,299 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.update.CdcrUpdateLog;
import org.apache.solr.update.UpdateLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The state of the replication with a target cluster.
*/
class CdcrReplicatorState {
private final String targetCollection;
private final String zkHost;
private final CloudSolrClient targetClient;
private CdcrUpdateLog.CdcrLogReader logReader;
private long consecutiveErrors = 0;
private final Map<ErrorType, Long> errorCounters = new HashMap<>();
private final FixedQueue<ErrorQueueEntry> errorsQueue = new FixedQueue<>(100); // keep the last 100 errors
private BenchmarkTimer benchmarkTimer;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final AtomicBoolean bootstrapInProgress = new AtomicBoolean(false);
private final AtomicInteger numBootstraps = new AtomicInteger();
CdcrReplicatorState(final String targetCollection, final String zkHost, final CloudSolrClient targetClient) {
this.targetCollection = targetCollection;
this.targetClient = targetClient;
this.zkHost = zkHost;
this.benchmarkTimer = new BenchmarkTimer();
}
/**
* Initialise the replicator state with a {@link org.apache.solr.update.CdcrUpdateLog.CdcrLogReader}
* that is positioned at the last target cluster checkpoint.
*/
void init(final CdcrUpdateLog.CdcrLogReader logReader) {
this.logReader = logReader;
}
void closeLogReader() {
if (logReader != null) {
logReader.close();
logReader = null;
}
}
CdcrUpdateLog.CdcrLogReader getLogReader() {
return logReader;
}
String getTargetCollection() {
return targetCollection;
}
String getZkHost() {
return zkHost;
}
CloudSolrClient getClient() {
return targetClient;
}
void shutdown() {
try {
targetClient.close();
} catch (IOException ioe) {
log.warn("Caught exception trying to close server: ", ioe);
}
this.closeLogReader();
}
void reportError(ErrorType error) {
if (!errorCounters.containsKey(error)) {
errorCounters.put(error, 0l);
}
errorCounters.put(error, errorCounters.get(error) + 1);
errorsQueue.add(new ErrorQueueEntry(error, new Date()));
consecutiveErrors++;
}
void resetConsecutiveErrors() {
consecutiveErrors = 0;
}
/**
* Returns the number of consecutive errors encountered while trying to forward updates to the target.
*/
long getConsecutiveErrors() {
return consecutiveErrors;
}
/**
* Gets the number of errors of a particular type.
*/
long getErrorCount(ErrorType type) {
if (errorCounters.containsKey(type)) {
return errorCounters.get(type);
} else {
return 0;
}
}
/**
* Gets the last errors ordered by timestamp (most recent first)
*/
List<String[]> getLastErrors() {
List<String[]> lastErrors = new ArrayList<>();
synchronized (errorsQueue) {
Iterator<ErrorQueueEntry> it = errorsQueue.iterator();
while (it.hasNext()) {
ErrorQueueEntry entry = it.next();
lastErrors.add(new String[]{entry.timestamp.toInstant().toString(), entry.type.toLower()});
}
}
return lastErrors;
}
/**
* Return the timestamp of the last processed operations
*/
String getTimestampOfLastProcessedOperation() {
if (logReader != null && logReader.getLastVersion() != -1) {
// Shift back to the right by 20 bits the version number - See VersionInfo#getNewClock
return Instant.ofEpochMilli(logReader.getLastVersion() >> 20).toString();
}
return "";
}
/**
* Gets the benchmark timer.
*/
BenchmarkTimer getBenchmarkTimer() {
return this.benchmarkTimer;
}
/**
* @return true if a bootstrap operation is in progress, false otherwise
*/
boolean isBootstrapInProgress() {
return bootstrapInProgress.get();
}
void setBootstrapInProgress(boolean inProgress) {
if (bootstrapInProgress.compareAndSet(true, false)) {
numBootstraps.incrementAndGet();
}
bootstrapInProgress.set(inProgress);
}
public int getNumBootstraps() {
return numBootstraps.get();
}
enum ErrorType {
INTERNAL,
BAD_REQUEST;
public String toLower() {
return toString().toLowerCase(Locale.ROOT);
}
}
static class BenchmarkTimer {
private long startTime;
private long runTime = 0;
private Map<Integer, Long> opCounters = new HashMap<>();
/**
* Start recording time.
*/
void start() {
startTime = System.nanoTime();
}
/**
* Stop recording time.
*/
void stop() {
runTime += System.nanoTime() - startTime;
startTime = -1;
}
void incrementCounter(final int operationType) {
switch (operationType) {
case UpdateLog.ADD:
case UpdateLog.DELETE:
case UpdateLog.DELETE_BY_QUERY: {
if (!opCounters.containsKey(operationType)) {
opCounters.put(operationType, 0l);
}
opCounters.put(operationType, opCounters.get(operationType) + 1);
return;
}
default:
}
}
long getRunTime() {
long totalRunTime = runTime;
if (startTime != -1) { // we are currently recording the time
totalRunTime += System.nanoTime() - startTime;
}
return totalRunTime;
}
double getOperationsPerSecond() {
long total = 0;
for (long counter : opCounters.values()) {
total += counter;
}
double elapsedTimeInSeconds = ((double) this.getRunTime() / 1E9);
return total / elapsedTimeInSeconds;
}
double getAddsPerSecond() {
long total = opCounters.get(UpdateLog.ADD) != null ? opCounters.get(UpdateLog.ADD) : 0;
double elapsedTimeInSeconds = ((double) this.getRunTime() / 1E9);
return total / elapsedTimeInSeconds;
}
double getDeletesPerSecond() {
long total = opCounters.get(UpdateLog.DELETE) != null ? opCounters.get(UpdateLog.DELETE) : 0;
total += opCounters.get(UpdateLog.DELETE_BY_QUERY) != null ? opCounters.get(UpdateLog.DELETE_BY_QUERY) : 0;
double elapsedTimeInSeconds = ((double) this.getRunTime() / 1E9);
return total / elapsedTimeInSeconds;
}
}
private static class ErrorQueueEntry {
private ErrorType type;
private Date timestamp;
private ErrorQueueEntry(ErrorType type, Date timestamp) {
this.type = type;
this.timestamp = timestamp;
}
}
private static class FixedQueue<E> extends LinkedList<E> {
private int maxSize;
public FixedQueue(int maxSize) {
this.maxSize = maxSize;
}
@Override
public synchronized boolean add(E e) {
super.addFirst(e);
if (size() > maxSize) {
removeLast();
}
return true;
}
}
}

View File

@ -1,880 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import java.io.Closeable;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkShardTerms;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.ZkCoreNodeProps;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.UpdateParams;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.IOUtils;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.apache.solr.core.CloseHook;
import org.apache.solr.core.PluginBag;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.update.CdcrUpdateLog;
import org.apache.solr.update.SolrCoreState;
import org.apache.solr.update.UpdateLog;
import org.apache.solr.update.VersionInfo;
import org.apache.solr.update.processor.DistributedUpdateProcessor;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.handler.admin.CoreAdminHandler.COMPLETED;
import static org.apache.solr.handler.admin.CoreAdminHandler.FAILED;
import static org.apache.solr.handler.admin.CoreAdminHandler.RESPONSE;
import static org.apache.solr.handler.admin.CoreAdminHandler.RESPONSE_MESSAGE;
import static org.apache.solr.handler.admin.CoreAdminHandler.RESPONSE_STATUS;
import static org.apache.solr.handler.admin.CoreAdminHandler.RUNNING;
/**
* <p>
* This request handler implements the CDCR API and is responsible of the execution of the
* {@link CdcrReplicator} threads.
* </p>
* <p>
* It relies on three classes, {@link org.apache.solr.handler.CdcrLeaderStateManager},
* {@link org.apache.solr.handler.CdcrBufferStateManager} and {@link org.apache.solr.handler.CdcrProcessStateManager}
* to synchronise the state of the CDCR across all the nodes.
* </p>
* <p>
* The CDCR process can be either {@link org.apache.solr.handler.CdcrParams.ProcessState#STOPPED} or {@link org.apache.solr.handler.CdcrParams.ProcessState#STARTED} by using the
* actions {@link org.apache.solr.handler.CdcrParams.CdcrAction#STOP} and {@link org.apache.solr.handler.CdcrParams.CdcrAction#START} respectively. If a node is leader and the process
* state is {@link org.apache.solr.handler.CdcrParams.ProcessState#STARTED}, the {@link CdcrReplicatorManager} will
* start the {@link CdcrReplicator} threads. If a node becomes non-leader or if the process state becomes
* {@link org.apache.solr.handler.CdcrParams.ProcessState#STOPPED}, the {@link CdcrReplicator} threads are stopped.
* </p>
* <p>
* The CDCR can be switched to a "buffering" mode, in which the update log will never delete old transaction log
* files. Such a mode can be enabled or disabled using the action {@link org.apache.solr.handler.CdcrParams.CdcrAction#ENABLEBUFFER} and
* {@link org.apache.solr.handler.CdcrParams.CdcrAction#DISABLEBUFFER} respectively.
* </p>
* <p>
* Known limitations: The source and target clusters must have the same topology. Replication between clusters
* with a different number of shards will likely results in an inconsistent index.
* </p>
* @deprecated since 8.6
*/
@Deprecated(since = "8.6")
public class CdcrRequestHandler extends RequestHandlerBase implements SolrCoreAware {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private SolrCore core;
private String collection;
private String shard;
private String path;
private SolrParams updateLogSynchronizerConfiguration;
private SolrParams replicatorConfiguration;
private SolrParams bufferConfiguration;
private Map<String, List<SolrParams>> replicasConfiguration;
private CdcrProcessStateManager processStateManager;
private CdcrBufferStateManager bufferStateManager;
private CdcrReplicatorManager replicatorManager;
private CdcrLeaderStateManager leaderStateManager;
private CdcrUpdateLogSynchronizer updateLogSynchronizer;
private CdcrBufferManager bufferManager;
@Override
public void init(@SuppressWarnings({"rawtypes"})NamedList args) {
super.init(args);
log.warn("CDCR (in its current form) is deprecated as of 8.6 and shall be removed in 9.0. See SOLR-14022 for details.");
if (args != null) {
// Configuration of the Update Log Synchronizer
Object updateLogSynchonizerParam = args.get(CdcrParams.UPDATE_LOG_SYNCHRONIZER_PARAM);
if (updateLogSynchonizerParam != null && updateLogSynchonizerParam instanceof NamedList) {
updateLogSynchronizerConfiguration = ((NamedList) updateLogSynchonizerParam).toSolrParams();
}
// Configuration of the Replicator
Object replicatorParam = args.get(CdcrParams.REPLICATOR_PARAM);
if (replicatorParam != null && replicatorParam instanceof NamedList) {
replicatorConfiguration = ((NamedList) replicatorParam).toSolrParams();
}
// Configuration of the Buffer
Object bufferParam = args.get(CdcrParams.BUFFER_PARAM);
if (bufferParam != null && bufferParam instanceof NamedList) {
bufferConfiguration = ((NamedList) bufferParam).toSolrParams();
}
// Configuration of the Replicas
replicasConfiguration = new HashMap<>();
@SuppressWarnings({"rawtypes"})
List replicas = args.getAll(CdcrParams.REPLICA_PARAM);
for (Object replica : replicas) {
if (replica != null && replica instanceof NamedList) {
SolrParams params = ((NamedList) replica).toSolrParams();
if (!replicasConfiguration.containsKey(params.get(CdcrParams.SOURCE_COLLECTION_PARAM))) {
replicasConfiguration.put(params.get(CdcrParams.SOURCE_COLLECTION_PARAM), new ArrayList<>());
}
replicasConfiguration.get(params.get(CdcrParams.SOURCE_COLLECTION_PARAM)).add(params);
}
}
}
}
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
// Pick the action
SolrParams params = req.getParams();
CdcrParams.CdcrAction action = null;
String a = params.get(CommonParams.ACTION);
if (a != null) {
action = CdcrParams.CdcrAction.get(a);
}
if (action == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + a);
}
switch (action) {
case START: {
this.handleStartAction(req, rsp);
break;
}
case STOP: {
this.handleStopAction(req, rsp);
break;
}
case STATUS: {
this.handleStatusAction(req, rsp);
break;
}
case COLLECTIONCHECKPOINT: {
this.handleCollectionCheckpointAction(req, rsp);
break;
}
case SHARDCHECKPOINT: {
this.handleShardCheckpointAction(req, rsp);
break;
}
case ENABLEBUFFER: {
this.handleEnableBufferAction(req, rsp);
break;
}
case DISABLEBUFFER: {
this.handleDisableBufferAction(req, rsp);
break;
}
case LASTPROCESSEDVERSION: {
this.handleLastProcessedVersionAction(req, rsp);
break;
}
case QUEUES: {
this.handleQueuesAction(req, rsp);
break;
}
case OPS: {
this.handleOpsAction(req, rsp);
break;
}
case ERRORS: {
this.handleErrorsAction(req, rsp);
break;
}
case BOOTSTRAP: {
this.handleBootstrapAction(req, rsp);
break;
}
case BOOTSTRAP_STATUS: {
this.handleBootstrapStatus(req, rsp);
break;
}
case CANCEL_BOOTSTRAP: {
this.handleCancelBootstrap(req, rsp);
break;
}
default: {
throw new RuntimeException("Unknown action: " + action);
}
}
rsp.setHttpCaching(false);
}
@Override
public void inform(SolrCore core) {
this.core = core;
collection = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
shard = core.getCoreDescriptor().getCloudDescriptor().getShardId();
// Make sure that the core is ZKAware
if (!core.getCoreContainer().isZooKeeperAware()) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Solr instance is not running in SolrCloud mode.");
}
// Make sure that the core is using the CdcrUpdateLog implementation
if (!(core.getUpdateHandler().getUpdateLog() instanceof CdcrUpdateLog)) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Solr instance is not configured with the cdcr update log.");
}
// Find the registered path of the handler
path = null;
for (Map.Entry<String, PluginBag.PluginHolder<SolrRequestHandler>> entry : core.getRequestHandlers().getRegistry().entrySet()) {
if (core.getRequestHandlers().isLoaded(entry.getKey()) && entry.getValue().get() == this) {
path = entry.getKey();
break;
}
}
if (path == null) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"The CdcrRequestHandler is not registered with the current core.");
}
if (!path.startsWith("/")) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"The CdcrRequestHandler needs to be registered to a path. Typically this is '/cdcr'");
}
// Initialisation phase
// If the Solr cloud is being initialised, each CDCR node will start up in its default state, i.e., STOPPED
// and non-leader. The leader state will be updated later, when all the Solr cores have been loaded.
// If the Solr cloud has already been initialised, and the core is reloaded (i.e., because a node died or a new node
// is added to the cluster), the CDCR node will synchronise its state with the global CDCR state that is stored
// in zookeeper.
// Initialise the buffer state manager
bufferStateManager = new CdcrBufferStateManager(core, bufferConfiguration);
// Initialise the process state manager
processStateManager = new CdcrProcessStateManager(core);
// Initialise the leader state manager
leaderStateManager = new CdcrLeaderStateManager(core);
// Initialise the replicator states manager
replicatorManager = new CdcrReplicatorManager(core, path, replicatorConfiguration, replicasConfiguration);
replicatorManager.setProcessStateManager(processStateManager);
replicatorManager.setLeaderStateManager(leaderStateManager);
// we need to inform it of a state event since the process and leader state
// may have been synchronised during the initialisation
replicatorManager.stateUpdate();
// Initialise the update log synchronizer
updateLogSynchronizer = new CdcrUpdateLogSynchronizer(core, path, updateLogSynchronizerConfiguration);
updateLogSynchronizer.setLeaderStateManager(leaderStateManager);
// we need to inform it of a state event since the leader state
// may have been synchronised during the initialisation
updateLogSynchronizer.stateUpdate();
// Initialise the buffer manager
bufferManager = new CdcrBufferManager(core);
bufferManager.setLeaderStateManager(leaderStateManager);
bufferManager.setBufferStateManager(bufferStateManager);
// we need to inform it of a state event since the leader state
// may have been synchronised during the initialisation
bufferManager.stateUpdate();
// register the close hook
this.registerCloseHook(core);
}
/**
* register a close hook to properly shutdown the state manager and scheduler
*/
private void registerCloseHook(SolrCore core) {
core.addCloseHook(new CloseHook() {
@Override
public void preClose(SolrCore core) {
log.info("Solr core is being closed - shutting down CDCR handler @ {}:{}", collection, shard);
updateLogSynchronizer.shutdown();
replicatorManager.shutdown();
bufferStateManager.shutdown();
processStateManager.shutdown();
leaderStateManager.shutdown();
}
@Override
public void postClose(SolrCore core) {
}
});
}
/**
* <p>
* Update and synchronize the process state.
* </p>
* <p>
* The process state manager must notify the replicator states manager of the change of state.
* </p>
*/
private void handleStartAction(SolrQueryRequest req, SolrQueryResponse rsp) {
if (processStateManager.getState() == CdcrParams.ProcessState.STOPPED) {
processStateManager.setState(CdcrParams.ProcessState.STARTED);
processStateManager.synchronize();
}
rsp.add(CdcrParams.CdcrAction.STATUS.toLower(), this.getStatus());
}
private void handleStopAction(SolrQueryRequest req, SolrQueryResponse rsp) {
if (processStateManager.getState() == CdcrParams.ProcessState.STARTED) {
processStateManager.setState(CdcrParams.ProcessState.STOPPED);
processStateManager.synchronize();
}
rsp.add(CdcrParams.CdcrAction.STATUS.toLower(), this.getStatus());
}
private void handleStatusAction(SolrQueryRequest req, SolrQueryResponse rsp) {
rsp.add(CdcrParams.CdcrAction.STATUS.toLower(), this.getStatus());
}
@SuppressWarnings({"unchecked", "rawtypes"})
private NamedList getStatus() {
NamedList status = new NamedList();
status.add(CdcrParams.ProcessState.getParam(), processStateManager.getState().toLower());
status.add(CdcrParams.BufferState.getParam(), bufferStateManager.getState().toLower());
return status;
}
/**
* This action is generally executed on the target cluster in order to retrieve the latest update checkpoint.
* This checkpoint is used on the source cluster to setup the
* {@link org.apache.solr.update.CdcrUpdateLog.CdcrLogReader} of a shard leader. <br/>
* This method will execute in parallel one
* {@link org.apache.solr.handler.CdcrParams.CdcrAction#SHARDCHECKPOINT} request per shard leader. It will
* then pick the lowest version number as checkpoint. Picking the lowest amongst all shards will ensure that we do not
* pick a checkpoint that is ahead of the source cluster. This can occur when other shard leaders are sending new
* updates to the target cluster while we are currently instantiating the
* {@link org.apache.solr.update.CdcrUpdateLog.CdcrLogReader}.
* This solution only works in scenarios where the topology of the source and target clusters are identical.
*/
private void handleCollectionCheckpointAction(SolrQueryRequest req, SolrQueryResponse rsp)
throws IOException, SolrServerException {
ZkController zkController = core.getCoreContainer().getZkController();
try {
zkController.getZkStateReader().forceUpdateCollection(collection);
} catch (Exception e) {
log.warn("Error when updating cluster state", e);
}
ClusterState cstate = zkController.getClusterState();
DocCollection docCollection = cstate.getCollectionOrNull(collection);
Collection<Slice> shards = docCollection == null? null : docCollection.getActiveSlices();
ExecutorService parallelExecutor = ExecutorUtil.newMDCAwareCachedThreadPool(new SolrNamedThreadFactory("parallelCdcrExecutor"));
long checkpoint = Long.MAX_VALUE;
try {
List<Callable<Long>> callables = new ArrayList<>();
for (Slice shard : shards) {
ZkNodeProps leaderProps = zkController.getZkStateReader().getLeaderRetry(collection, shard.getName());
ZkCoreNodeProps nodeProps = new ZkCoreNodeProps(leaderProps);
callables.add(new SliceCheckpointCallable(nodeProps.getCoreUrl(), path));
}
for (final Future<Long> future : parallelExecutor.invokeAll(callables)) {
long version = future.get();
if (version < checkpoint) { // we must take the lowest checkpoint from all the shards
checkpoint = version;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Error while requesting shard's checkpoints", e);
} catch (ExecutionException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Error while requesting shard's checkpoints", e);
} finally {
parallelExecutor.shutdown();
}
rsp.add(CdcrParams.CHECKPOINT, checkpoint);
}
/**
* Retrieve the version number of the latest entry of the {@link org.apache.solr.update.UpdateLog}.
*/
private void handleShardCheckpointAction(SolrQueryRequest req, SolrQueryResponse rsp) {
if (!leaderStateManager.amILeader()) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Action '" + CdcrParams.CdcrAction.SHARDCHECKPOINT +
"' sent to non-leader replica");
}
UpdateLog ulog = core.getUpdateHandler().getUpdateLog();
VersionInfo versionInfo = ulog.getVersionInfo();
try (UpdateLog.RecentUpdates recentUpdates = ulog.getRecentUpdates()) {
long maxVersionFromRecent = recentUpdates.getMaxRecentVersion();
long maxVersionFromIndex = versionInfo.getMaxVersionFromIndex(req.getSearcher());
log.info("Found maxVersionFromRecent {} maxVersionFromIndex {}", maxVersionFromRecent, maxVersionFromIndex);
// there is no race with ongoing bootstrap because we don't expect any updates to come from the source
long maxVersion = Math.max(maxVersionFromIndex, maxVersionFromRecent);
if (maxVersion == 0L) {
maxVersion = -1;
}
rsp.add(CdcrParams.CHECKPOINT, maxVersion);
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Action '" + CdcrParams.CdcrAction.SHARDCHECKPOINT +
"' could not read max version");
}
}
private void handleEnableBufferAction(SolrQueryRequest req, SolrQueryResponse rsp) {
if (bufferStateManager.getState() == CdcrParams.BufferState.DISABLED) {
bufferStateManager.setState(CdcrParams.BufferState.ENABLED);
bufferStateManager.synchronize();
}
rsp.add(CdcrParams.CdcrAction.STATUS.toLower(), this.getStatus());
}
private void handleDisableBufferAction(SolrQueryRequest req, SolrQueryResponse rsp) {
if (bufferStateManager.getState() == CdcrParams.BufferState.ENABLED) {
bufferStateManager.setState(CdcrParams.BufferState.DISABLED);
bufferStateManager.synchronize();
}
rsp.add(CdcrParams.CdcrAction.STATUS.toLower(), this.getStatus());
}
/**
* <p>
* We have to take care of four cases:
* <ul>
* <li>Replication & Buffering</li>
* <li>Replication & No Buffering</li>
* <li>No Replication & Buffering</li>
* <li>No Replication & No Buffering</li>
* </ul>
* In the first three cases, at least one log reader should have been initialised. We should take the lowest
* last processed version across all the initialised readers. In the last case, there isn't a log reader
* initialised. We should instantiate one and get the version of the first entries.
* </p>
*/
private void handleLastProcessedVersionAction(SolrQueryRequest req, SolrQueryResponse rsp) {
String collectionName = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
String shard = core.getCoreDescriptor().getCloudDescriptor().getShardId();
if (!leaderStateManager.amILeader()) {
log.warn("Action {} sent to non-leader replica @ {}:{}", CdcrParams.CdcrAction.LASTPROCESSEDVERSION, collectionName, shard);
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Action " + CdcrParams.CdcrAction.LASTPROCESSEDVERSION +
" sent to non-leader replica");
}
// take care of the first three cases
// first check the log readers from the replicator states
long lastProcessedVersion = Long.MAX_VALUE;
for (CdcrReplicatorState state : replicatorManager.getReplicatorStates()) {
long version = Long.MAX_VALUE;
if (state.getLogReader() != null) {
version = state.getLogReader().getLastVersion();
}
lastProcessedVersion = Math.min(lastProcessedVersion, version);
}
// next check the log reader of the buffer
CdcrUpdateLog.CdcrLogReader bufferLogReader = ((CdcrUpdateLog) core.getUpdateHandler().getUpdateLog()).getBufferToggle();
if (bufferLogReader != null) {
lastProcessedVersion = Math.min(lastProcessedVersion, bufferLogReader.getLastVersion());
}
// the fourth case: no cdc replication, no buffering: all readers were null
if (processStateManager.getState().equals(CdcrParams.ProcessState.STOPPED) &&
bufferStateManager.getState().equals(CdcrParams.BufferState.DISABLED)) {
CdcrUpdateLog.CdcrLogReader logReader = ((CdcrUpdateLog) core.getUpdateHandler().getUpdateLog()).newLogReader();
try {
// let the reader initialize lastVersion
logReader.next();
lastProcessedVersion = Math.min(lastProcessedVersion, logReader.getLastVersion());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Error while fetching the last processed version", e);
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Error while fetching the last processed version", e);
} finally {
logReader.close();
}
}
log.debug("Returning the lowest last processed version {} @ {}:{}", lastProcessedVersion, collectionName, shard);
rsp.add(CdcrParams.LAST_PROCESSED_VERSION, lastProcessedVersion);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void handleQueuesAction(SolrQueryRequest req, SolrQueryResponse rsp) {
NamedList hosts = new NamedList();
for (CdcrReplicatorState state : replicatorManager.getReplicatorStates()) {
NamedList queueStats = new NamedList();
CdcrUpdateLog.CdcrLogReader logReader = state.getLogReader();
if (logReader == null) {
String collectionName = req.getCore().getCoreDescriptor().getCloudDescriptor().getCollectionName();
String shard = req.getCore().getCoreDescriptor().getCloudDescriptor().getShardId();
log.warn("The log reader for target collection {} is not initialised @ {}:{}",
state.getTargetCollection(), collectionName, shard);
queueStats.add(CdcrParams.QUEUE_SIZE, -1l);
} else {
queueStats.add(CdcrParams.QUEUE_SIZE, logReader.getNumberOfRemainingRecords());
}
queueStats.add(CdcrParams.LAST_TIMESTAMP, state.getTimestampOfLastProcessedOperation());
if (hosts.get(state.getZkHost()) == null) {
hosts.add(state.getZkHost(), new NamedList());
}
((NamedList) hosts.get(state.getZkHost())).add(state.getTargetCollection(), queueStats);
}
rsp.add(CdcrParams.QUEUES, hosts);
UpdateLog updateLog = core.getUpdateHandler().getUpdateLog();
rsp.add(CdcrParams.TLOG_TOTAL_SIZE, updateLog.getTotalLogsSize());
rsp.add(CdcrParams.TLOG_TOTAL_COUNT, updateLog.getTotalLogsNumber());
rsp.add(CdcrParams.UPDATE_LOG_SYNCHRONIZER,
updateLogSynchronizer.isStarted() ? CdcrParams.ProcessState.STARTED.toLower() : CdcrParams.ProcessState.STOPPED.toLower());
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void handleOpsAction(SolrQueryRequest req, SolrQueryResponse rsp) {
NamedList hosts = new NamedList();
for (CdcrReplicatorState state : replicatorManager.getReplicatorStates()) {
NamedList ops = new NamedList();
ops.add(CdcrParams.COUNTER_ALL, state.getBenchmarkTimer().getOperationsPerSecond());
ops.add(CdcrParams.COUNTER_ADDS, state.getBenchmarkTimer().getAddsPerSecond());
ops.add(CdcrParams.COUNTER_DELETES, state.getBenchmarkTimer().getDeletesPerSecond());
if (hosts.get(state.getZkHost()) == null) {
hosts.add(state.getZkHost(), new NamedList());
}
((NamedList) hosts.get(state.getZkHost())).add(state.getTargetCollection(), ops);
}
rsp.add(CdcrParams.OPERATIONS_PER_SECOND, hosts);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void handleErrorsAction(SolrQueryRequest req, SolrQueryResponse rsp) {
NamedList hosts = new NamedList();
for (CdcrReplicatorState state : replicatorManager.getReplicatorStates()) {
NamedList errors = new NamedList();
errors.add(CdcrParams.CONSECUTIVE_ERRORS, state.getConsecutiveErrors());
errors.add(CdcrReplicatorState.ErrorType.BAD_REQUEST.toLower(), state.getErrorCount(CdcrReplicatorState.ErrorType.BAD_REQUEST));
errors.add(CdcrReplicatorState.ErrorType.INTERNAL.toLower(), state.getErrorCount(CdcrReplicatorState.ErrorType.INTERNAL));
NamedList lastErrors = new NamedList();
for (String[] lastError : state.getLastErrors()) {
lastErrors.add(lastError[0], lastError[1]);
}
errors.add(CdcrParams.LAST, lastErrors);
if (hosts.get(state.getZkHost()) == null) {
hosts.add(state.getZkHost(), new NamedList());
}
((NamedList) hosts.get(state.getZkHost())).add(state.getTargetCollection(), errors);
}
rsp.add(CdcrParams.ERRORS, hosts);
}
private void handleBootstrapAction(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException, InterruptedException, SolrServerException {
String collectionName = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
String shard = core.getCoreDescriptor().getCloudDescriptor().getShardId();
if (!leaderStateManager.amILeader()) {
log.warn("Action {} sent to non-leader replica @ {}:{}", CdcrParams.CdcrAction.BOOTSTRAP, collectionName, shard);
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Action " + CdcrParams.CdcrAction.BOOTSTRAP +
" sent to non-leader replica");
}
CountDownLatch latch = new CountDownLatch(1); // latch to make sure BOOTSTRAP_STATUS gives correct response
Runnable runnable = () -> {
Lock recoveryLock = req.getCore().getSolrCoreState().getRecoveryLock();
boolean locked = recoveryLock.tryLock();
SolrCoreState coreState = core.getSolrCoreState();
try {
if (!locked) {
handleCancelBootstrap(req, rsp);
} else if (leaderStateManager.amILeader()) {
coreState.setCdcrBootstrapRunning(true);
latch.countDown(); // free the latch as current bootstrap is executing
//running.set(true);
String leaderUrl = ReplicationHandler.getObjectWithBackwardCompatibility(req.getParams(), ReplicationHandler.LEADER_URL, ReplicationHandler.LEGACY_LEADER_URL, null);
BootstrapCallable bootstrapCallable = new BootstrapCallable(leaderUrl, core);
coreState.setCdcrBootstrapCallable(bootstrapCallable);
Future<Boolean> bootstrapFuture = core.getCoreContainer().getUpdateShardHandler().getRecoveryExecutor()
.submit(bootstrapCallable);
coreState.setCdcrBootstrapFuture(bootstrapFuture);
try {
bootstrapFuture.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Bootstrap was interrupted", e);
} catch (ExecutionException e) {
log.error("Bootstrap operation failed", e);
}
} else {
log.error("Action {} sent to non-leader replica @ {}:{}. Aborting bootstrap.", CdcrParams.CdcrAction.BOOTSTRAP, collectionName, shard);
}
} finally {
if (locked) {
coreState.setCdcrBootstrapRunning(false);
recoveryLock.unlock();
} else {
latch.countDown(); // free the latch as current bootstrap is executing
}
}
};
try {
core.getCoreContainer().getUpdateShardHandler().getUpdateExecutor().submit(runnable);
rsp.add(RESPONSE_STATUS, "submitted");
latch.await(10000, TimeUnit.MILLISECONDS); // put the latch for current bootstrap command
} catch (RejectedExecutionException ree) {
// no problem, we're probably shutting down
rsp.add(RESPONSE_STATUS, "failed");
}
}
private void handleCancelBootstrap(SolrQueryRequest req, SolrQueryResponse rsp) {
BootstrapCallable callable = (BootstrapCallable)core.getSolrCoreState().getCdcrBootstrapCallable();
IOUtils.closeQuietly(callable);
rsp.add(RESPONSE_STATUS, "cancelled");
}
private void handleBootstrapStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException, SolrServerException {
SolrCoreState coreState = core.getSolrCoreState();
if (coreState.getCdcrBootstrapRunning()) {
rsp.add(RESPONSE_STATUS, RUNNING);
return;
}
Future<Boolean> future = coreState.getCdcrBootstrapFuture();
BootstrapCallable callable = (BootstrapCallable)coreState.getCdcrBootstrapCallable();
if (future == null) {
rsp.add(RESPONSE_STATUS, "notfound");
rsp.add(RESPONSE_MESSAGE, "No bootstrap found in running, completed or failed states");
} else if (future.isCancelled() || callable.isClosed()) {
rsp.add(RESPONSE_STATUS, "cancelled");
} else if (future.isDone()) {
// could be a normal termination or an exception
try {
Boolean result = future.get();
if (result) {
rsp.add(RESPONSE_STATUS, COMPLETED);
} else {
rsp.add(RESPONSE_STATUS, FAILED);
}
} catch (InterruptedException e) {
// should not happen?
} catch (ExecutionException e) {
rsp.add(RESPONSE_STATUS, FAILED);
rsp.add(RESPONSE, e);
} catch (CancellationException ce) {
rsp.add(RESPONSE_STATUS, FAILED);
rsp.add(RESPONSE_MESSAGE, "Bootstrap was cancelled");
}
} else {
rsp.add(RESPONSE_STATUS, RUNNING);
}
}
static class BootstrapCallable implements Callable<Boolean>, Closeable {
private final String leaderUrl;
private final SolrCore core;
private volatile boolean closed = false;
BootstrapCallable(String leaderUrl, SolrCore core) {
this.leaderUrl = leaderUrl;
this.core = core;
}
@Override
public void close() throws IOException {
closed = true;
SolrRequestHandler handler = core.getRequestHandler(ReplicationHandler.PATH);
ReplicationHandler replicationHandler = (ReplicationHandler) handler;
replicationHandler.abortFetch();
}
public boolean isClosed() {
return closed;
}
@Override
public Boolean call() throws Exception {
boolean success = false;
UpdateLog ulog = core.getUpdateHandler().getUpdateLog();
// we start buffering updates as a safeguard however we do not expect
// to receive any updates from the source during bootstrap
ulog.bufferUpdates();
try {
commitOnLeader(leaderUrl);
// use rep handler directly, so we can do this sync rather than async
SolrRequestHandler handler = core.getRequestHandler(ReplicationHandler.PATH);
ReplicationHandler replicationHandler = (ReplicationHandler) handler;
if (replicationHandler == null) {
throw new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE,
"Skipping recovery, no " + ReplicationHandler.PATH + " handler found");
}
ModifiableSolrParams solrParams = new ModifiableSolrParams();
solrParams.set(ReplicationHandler.LEADER_URL, leaderUrl);
// we do not want the raw tlog files from the source
solrParams.set(ReplicationHandler.TLOG_FILES, false);
success = replicationHandler.doFetch(solrParams, false).getSuccessful();
Future<UpdateLog.RecoveryInfo> future = ulog.applyBufferedUpdates();
if (future == null) {
// no replay needed
log.info("No replay needed.");
} else {
log.info("Replaying buffered documents.");
// wait for replay
UpdateLog.RecoveryInfo report = future.get();
if (report.failed) {
SolrException.log(log, "Replay failed");
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Replay failed");
}
}
if (success) {
ZkController zkController = core.getCoreContainer().getZkController();
String collectionName = core.getCoreDescriptor().getCollectionName();
ClusterState clusterState = zkController.getZkStateReader().getClusterState();
DocCollection collection = clusterState.getCollection(collectionName);
Slice slice = collection.getSlice(core.getCoreDescriptor().getCloudDescriptor().getShardId());
ZkShardTerms terms = zkController.getShardTerms(collectionName, slice.getName());
String coreNodeName = core.getCoreDescriptor().getCloudDescriptor().getCoreNodeName();
Set<String> allExceptLeader = slice.getReplicas().stream().filter(replica -> !replica.getName().equals(coreNodeName)).map(Replica::getName).collect(Collectors.toSet());
terms.ensureTermsIsHigher(coreNodeName, allExceptLeader);
}
return success;
} finally {
if (closed || !success) {
// we cannot apply the buffer in this case because it will introduce newer versions in the
// update log and then the source cluster will get those versions via collectioncheckpoint
// causing the versions in between to be completely missed
boolean dropped = ulog.dropBufferedUpdates();
assert dropped;
}
}
}
private void commitOnLeader(String leaderUrl) throws SolrServerException,
IOException {
try (HttpSolrClient client = new HttpSolrClient.Builder(leaderUrl)
.withConnectionTimeout(30000)
.build()) {
UpdateRequest ureq = new UpdateRequest();
ureq.setParams(new ModifiableSolrParams());
ureq.getParams().set(DistributedUpdateProcessor.COMMIT_END_POINT, true);
ureq.getParams().set(UpdateParams.OPEN_SEARCHER, false);
ureq.setAction(AbstractUpdateRequest.ACTION.COMMIT, false, true).process(
client);
}
}
}
@Override
public String getDescription() {
return "Manage Cross Data Center Replication";
}
@Override
public Category getCategory() {
return Category.REPLICATION;
}
/**
* A thread subclass for executing a single
* {@link org.apache.solr.handler.CdcrParams.CdcrAction#SHARDCHECKPOINT} action.
*/
private static final class SliceCheckpointCallable implements Callable<Long> {
final String baseUrl;
final String cdcrPath;
SliceCheckpointCallable(final String baseUrl, final String cdcrPath) {
this.baseUrl = baseUrl;
this.cdcrPath = cdcrPath;
}
@Override
public Long call() throws Exception {
try (HttpSolrClient server = new HttpSolrClient.Builder(baseUrl)
.withConnectionTimeout(15000)
.withSocketTimeout(60000)
.build()) {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CommonParams.ACTION, CdcrParams.CdcrAction.SHARDCHECKPOINT.toString());
@SuppressWarnings({"rawtypes"})
SolrRequest request = new QueryRequest(params);
request.setPath(cdcrPath);
@SuppressWarnings({"rawtypes"})
NamedList response = server.request(request);
return (Long) response.get(CdcrParams.CHECKPOINT);
}
}
}
}

View File

@ -1,47 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import java.util.ArrayList;
import java.util.List;
/**
* A state manager which implements an observer pattern to notify observers
* of a state change.
*/
abstract class CdcrStateManager {
private List<CdcrStateObserver> observers = new ArrayList<>();
void register(CdcrStateObserver observer) {
this.observers.add(observer);
}
void callback() {
for (CdcrStateObserver observer : observers) {
observer.stateUpdate();
}
}
interface CdcrStateObserver {
void stateUpdate();
}
}

View File

@ -1,192 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.ZkCoreNodeProps;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrCore;
import org.apache.solr.update.CdcrUpdateLog;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Synchronize periodically the update log of non-leader nodes with their leaders.
* </p>
* <p>
* Non-leader nodes must always buffer updates in case of leader failures. They have to periodically
* synchronize their update logs with their leader to remove old transaction logs that will never be used anymore.
* This is performed by a background thread that is scheduled with a fixed delay. The background thread is sending
* the action {@link org.apache.solr.handler.CdcrParams.CdcrAction#LASTPROCESSEDVERSION} to the leader to retrieve
* the lowest last version number processed. This version is then used to move forward the buffer log reader.
* </p>
*/
class CdcrUpdateLogSynchronizer implements CdcrStateManager.CdcrStateObserver {
private CdcrLeaderStateManager leaderStateManager;
private ScheduledExecutorService scheduler;
private final SolrCore core;
private final String collection;
private final String shardId;
private final String path;
private int timeSchedule = DEFAULT_TIME_SCHEDULE;
private static final int DEFAULT_TIME_SCHEDULE = 60000; // by default, every minute
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
CdcrUpdateLogSynchronizer(SolrCore core, String path, SolrParams updateLogSynchonizerConfiguration) {
this.core = core;
this.path = path;
this.collection = core.getCoreDescriptor().getCloudDescriptor().getCollectionName();
this.shardId = core.getCoreDescriptor().getCloudDescriptor().getShardId();
if (updateLogSynchonizerConfiguration != null) {
this.timeSchedule = updateLogSynchonizerConfiguration.getInt(CdcrParams.SCHEDULE_PARAM, DEFAULT_TIME_SCHEDULE);
}
}
void setLeaderStateManager(final CdcrLeaderStateManager leaderStateManager) {
this.leaderStateManager = leaderStateManager;
this.leaderStateManager.register(this);
}
@Override
public void stateUpdate() {
// If I am not the leader, I need to synchronise periodically my update log with my leader.
if (!leaderStateManager.amILeader()) {
scheduler = Executors.newSingleThreadScheduledExecutor(new SolrNamedThreadFactory("cdcr-update-log-synchronizer"));
scheduler.scheduleWithFixedDelay(new UpdateLogSynchronisation(), 0, timeSchedule, TimeUnit.MILLISECONDS);
return;
}
this.shutdown();
}
boolean isStarted() {
return scheduler != null;
}
void shutdown() {
if (scheduler != null) {
// interrupts are often dangerous in Lucene / Solr code, but the
// test for this will leak threads without
scheduler.shutdownNow();
scheduler = null;
}
}
private class UpdateLogSynchronisation implements Runnable {
private String getLeaderUrl() {
ZkController zkController = core.getCoreContainer().getZkController();
ClusterState cstate = zkController.getClusterState();
DocCollection docCollection = cstate.getCollection(collection);
ZkNodeProps leaderProps = docCollection.getLeader(shardId);
if (leaderProps == null) { // we might not have a leader yet, returns null
return null;
}
ZkCoreNodeProps nodeProps = new ZkCoreNodeProps(leaderProps);
return nodeProps.getCoreUrl();
}
@Override
public void run() {
try {
String leaderUrl = getLeaderUrl();
if (leaderUrl == null) { // we might not have a leader yet, stop and try again later
return;
}
HttpSolrClient server = new HttpSolrClient.Builder(leaderUrl)
.withConnectionTimeout(15000)
.withSocketTimeout(60000)
.build();
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CommonParams.ACTION, CdcrParams.CdcrAction.LASTPROCESSEDVERSION.toString());
@SuppressWarnings({"rawtypes"})
SolrRequest request = new QueryRequest(params);
request.setPath(path);
long lastVersion;
try {
@SuppressWarnings({"rawtypes"})
NamedList response = server.request(request);
lastVersion = (Long) response.get(CdcrParams.LAST_PROCESSED_VERSION);
if (log.isDebugEnabled()) {
log.debug("My leader {} says its last processed _version_ number is: {}. I am {}", leaderUrl, lastVersion,
core.getCoreDescriptor().getCloudDescriptor().getCoreNodeName());
}
} catch (IOException | SolrServerException e) {
log.warn("Couldn't get last processed version from leader {}: ", leaderUrl, e);
return;
} finally {
try {
server.close();
} catch (IOException ioe) {
log.warn("Caught exception trying to close client to {}: ", leaderUrl, ioe);
}
}
// if we received -1, it means that the log reader on the leader has not yet started to read log entries
// do nothing
if (lastVersion == -1) {
return;
}
try {
CdcrUpdateLog ulog = (CdcrUpdateLog) core.getUpdateHandler().getUpdateLog();
if (ulog.isBuffering()) {
log.debug("Advancing replica buffering tlog reader to {} @ {}:{}", lastVersion, collection, shardId);
ulog.getBufferToggle().seek(lastVersion);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Couldn't advance replica buffering tlog reader to {} (to remove old tlogs): ", lastVersion, e);
} catch (IOException e) {
log.warn("Couldn't advance replica buffering tlog reader to {} (to remove old tlogs): ", lastVersion, e);
}
} catch (Throwable e) {
log.warn("Caught unexpected exception", e);
throw e;
}
}
}
}

View File

@ -93,10 +93,7 @@ import org.apache.solr.core.SolrCore;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.update.CdcrUpdateLog;
import org.apache.solr.update.CommitUpdateCommand;
import org.apache.solr.update.UpdateLog;
import org.apache.solr.update.VersionInfo;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.apache.solr.util.FileUtils;
import org.apache.solr.util.PropertiesOutputStream;
@ -137,14 +134,10 @@ public class IndexFetcher {
private volatile List<Map<String, Object>> confFilesToDownload;
private volatile List<Map<String, Object>> tlogFilesToDownload;
private volatile List<Map<String, Object>> filesDownloaded;
private volatile List<Map<String, Object>> confFilesDownloaded;
private volatile List<Map<String, Object>> tlogFilesDownloaded;
private volatile Map<String, Object> currentFile;
private volatile DirectoryFileFetcher dirFileFetcher;
@ -263,10 +256,6 @@ public class IndexFetcher {
soTimeout = getParameter(initArgs, HttpClientUtil.PROP_SO_TIMEOUT, 120000, null);
}
if (initArgs.getBooleanArg(TLOG_FILES) != null) {
downloadTlogFiles = initArgs.getBooleanArg(TLOG_FILES);
}
String httpBasicAuthUser = (String) initArgs.get(HttpClientUtil.PROP_BASIC_AUTH_USER);
String httpBasicAuthPassword = (String) initArgs.get(HttpClientUtil.PROP_BASIC_AUTH_PASS);
myHttpClient = createHttpClient(solrCore, httpBasicAuthUser, httpBasicAuthPassword, useExternalCompression);
@ -314,7 +303,6 @@ public class IndexFetcher {
private void fetchFileList(long gen) throws IOException {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(COMMAND, CMD_GET_FILE_LIST);
params.set(TLOG_FILES, downloadTlogFiles);
params.set(GENERATION, String.valueOf(gen));
params.set(CommonParams.WT, JAVABIN);
params.set(CommonParams.QT, ReplicationHandler.PATH);
@ -340,11 +328,6 @@ public class IndexFetcher {
files = (List<Map<String,Object>>) response.get(CONF_FILES);
if (files != null)
confFilesToDownload = Collections.synchronizedList(files);
files = (List<Map<String, Object>>) response.get(TLOG_FILES);
if (files != null) {
tlogFilesToDownload = Collections.synchronizedList(files);
}
} catch (SolrServerException e) {
throw new IOException(e);
}
@ -500,11 +483,6 @@ public class IndexFetcher {
if (log.isInfoEnabled()) {
log.info("Number of files in latest index in leader: {}", filesToDownload.size());
}
if (tlogFilesToDownload != null) {
if (log.isInfoEnabled()) {
log.info("Number of tlog files in leader: {}", tlogFilesToDownload.size());
}
}
// Create the sync service
fsyncService = ExecutorUtil.newMDCAwareSingleThreadExecutor(new SolrNamedThreadFactory("fsyncService"));
@ -522,11 +500,6 @@ public class IndexFetcher {
tmpIndexDir = solrCore.getDirectoryFactory().get(tmpIndexDirPath, DirContext.DEFAULT, solrCore.getSolrConfig().indexConfig.lockType);
// tmp dir for tlog files
if (tlogFilesToDownload != null) {
tmpTlogDir = new File(solrCore.getUpdateHandler().getUpdateLog().getLogDir(), "tlog." + timestamp);
}
// cindex dir...
indexDirPath = solrCore.getIndexDir();
indexDir = solrCore.getDirectoryFactory().get(indexDirPath, DirContext.DEFAULT, solrCore.getSolrConfig().indexConfig.lockType);
@ -588,10 +561,6 @@ public class IndexFetcher {
long bytesDownloaded = downloadIndexFiles(isFullCopyNeeded, indexDir,
tmpIndexDir, indexDirPath, tmpIndexDirPath, latestGeneration);
if (tlogFilesToDownload != null) {
bytesDownloaded += downloadTlogFiles(tmpTlogDir, latestGeneration);
reloadCore = true; // reload update log
}
final long timeTakenSeconds = getReplicationTimeElapsed();
final Long bytesDownloadedPerSecond = (timeTakenSeconds != 0 ? Long.valueOf(bytesDownloaded / timeTakenSeconds) : null);
log.info("Total time taken for download (fullCopy={},bytesDownloaded={}) : {} secs ({} bytes/sec) to {}",
@ -607,10 +576,6 @@ public class IndexFetcher {
} else {
successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
}
if (tlogFilesToDownload != null) {
// move tlog files and refresh ulog only if we successfully installed a new index
successfulInstall &= moveTlogFiles(tmpTlogDir);
}
if (successfulInstall) {
if (isFullCopyNeeded) {
// let the system know we are changing dir's and the old one
@ -637,10 +602,6 @@ public class IndexFetcher {
} else {
successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
}
if (tlogFilesToDownload != null) {
// move tlog files and refresh ulog only if we successfully installed a new index
successfulInstall &= moveTlogFiles(tmpTlogDir);
}
if (successfulInstall) {
logReplicationTimeAndConfFiles(modifiedConfFiles,
successfulInstall);
@ -733,7 +694,7 @@ public class IndexFetcher {
core.getUpdateHandler().getSolrCoreState().setLastReplicateIndexSuccess(successfulInstall);
}
filesToDownload = filesDownloaded = confFilesDownloaded = confFilesToDownload = tlogFilesToDownload = tlogFilesDownloaded = null;
filesToDownload = filesDownloaded = confFilesDownloaded = confFilesToDownload = null;
markReplicationStop();
dirFileFetcher = null;
localFileFetcher = null;
@ -989,30 +950,6 @@ public class IndexFetcher {
}
}
/**
* Download all the tlog files to the temp tlog directory.
*/
private long downloadTlogFiles(File tmpTlogDir, long latestGeneration) throws Exception {
log.info("Starting download of tlog files from leader: {}", tlogFilesToDownload);
tlogFilesDownloaded = Collections.synchronizedList(new ArrayList<>());
long bytesDownloaded = 0;
boolean status = tmpTlogDir.mkdirs();
if (!status) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Failed to create temporary tlog folder: " + tmpTlogDir.getName());
}
for (Map<String, Object> file : tlogFilesToDownload) {
String saveAs = (String) (file.get(ALIAS) == null ? file.get(NAME) : file.get(ALIAS));
localFileFetcher = new LocalFsFileFetcher(tmpTlogDir, file, saveAs, TLOG_FILE, latestGeneration);
currentFile = file;
localFileFetcher.fetchFile();
bytesDownloaded += localFileFetcher.getBytesDownloaded();
tlogFilesDownloaded.add(new HashMap<>(file));
}
return bytesDownloaded;
}
/**
* Download the index files. If a new index is needed, download all the files.
*
@ -1339,50 +1276,6 @@ public class IndexFetcher {
return true;
}
/**
* <p>
* Copy all the tlog files from the temp tlog dir to the actual tlog dir, and reset
* the {@link UpdateLog}. The copy will try to preserve the original tlog directory
* if the copy fails.
* </p>
* <p>
* This assumes that the tlog files transferred from the leader are in synch with the
* index files transferred from the leader. The reset of the update log relies on the version
* of the latest operations found in the tlog files. If the tlogs are ahead of the latest commit
* point, it will not copy all the needed buffered updates for the replay and it will miss
* some operations.
* </p>
*/
private boolean moveTlogFiles(File tmpTlogDir) {
UpdateLog ulog = solrCore.getUpdateHandler().getUpdateLog();
VersionInfo vinfo = ulog.getVersionInfo();
vinfo.blockUpdates(); // block updates until the new update log is initialised
try {
// reset the update log before copying the new tlog directory
CdcrUpdateLog.BufferedUpdates bufferedUpdates = ((CdcrUpdateLog) ulog).resetForRecovery();
// try to move the temp tlog files to the tlog directory
if (!copyTmpTlogFiles2Tlog(tmpTlogDir)) return false;
// reinitialise the update log and copy the buffered updates
if (bufferedUpdates.tlog != null) {
// map file path to its new backup location
File parentDir = FileSystems.getDefault().getPath(solrCore.getUpdateHandler().getUpdateLog().getLogDir()).getParent().toFile();
File backupTlogDir = new File(parentDir, tmpTlogDir.getName());
bufferedUpdates.tlog = new File(backupTlogDir, bufferedUpdates.tlog.getName());
}
// init the update log with the new set of tlog files, and copy the buffered updates
((CdcrUpdateLog) ulog).initForRecovery(bufferedUpdates.tlog, bufferedUpdates.offset);
}
catch (Exception e) {
log.error("Unable to copy tlog files", e);
return false;
}
finally {
vinfo.unblockUpdates();
}
return true;
}
/**
* Make file list
*/
@ -1570,20 +1463,7 @@ public class IndexFetcher {
return timeElapsed;
}
List<Map<String, Object>> getTlogFilesToDownload() {
//make a copy first because it can be null later
List<Map<String, Object>> tmp = tlogFilesToDownload;
//create a new instance. or else iterator may fail
return tmp == null ? Collections.emptyList() : new ArrayList<>(tmp);
}
List<Map<String, Object>> getTlogFilesDownloaded() {
//make a copy first because it can be null later
List<Map<String, Object>> tmp = tlogFilesDownloaded;
// NOTE: it's safe to make a copy of a SynchronizedCollection(ArrayList)
return tmp == null ? Collections.emptyList() : new ArrayList<>(tmp);
}
@SuppressWarnings({"unchecked"})
List<Map<String, Object>> getConfFilesToDownload() {
//make a copy first because it can be null later
List<Map<String, Object>> tmp = confFilesToDownload;

View File

@ -96,7 +96,6 @@ import org.apache.solr.metrics.SolrMetricsContext;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.update.CdcrUpdateLog;
import org.apache.solr.update.SolrIndexWriter;
import org.apache.solr.update.VersionInfo;
import org.apache.solr.common.util.SolrNamedThreadFactory;
@ -731,19 +730,6 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
}
rsp.add(CMD_GET_FILE_LIST, result);
if (solrParams.getBool(TLOG_FILES, false)) {
try {
List<Map<String, Object>> tlogfiles = getTlogFileList(commit);
log.info("Adding tlog files to list: {}", tlogfiles);
rsp.add(TLOG_FILES, tlogfiles);
}
catch (IOException e) {
log.error("Unable to get tlog file names for indexCommit generation: {}", commit.getGeneration(), e);
reportErrorOnResponse(rsp, "unable to get tlog file names for given index generation", e);
return;
}
}
if (confFileNameAlias.size() < 1 || core.getCoreContainer().isZooKeeperAware())
return;
log.debug("Adding config files to list: {}", includeConfFiles);
@ -762,29 +748,6 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
}
}
/**
* Retrieves the list of tlog files associated to a commit point.
* NOTE: The commit <b>MUST</b> be reserved before calling this method
*/
List<Map<String, Object>> getTlogFileList(IndexCommit commit) throws IOException {
long maxVersion = this.getMaxVersion(commit);
CdcrUpdateLog ulog = (CdcrUpdateLog) core.getUpdateHandler().getUpdateLog();
String[] logList = ulog.getLogList(new File(ulog.getLogDir()));
List<Map<String, Object>> tlogFiles = new ArrayList<>();
for (String fileName : logList) {
// filter out tlogs that are older than the current index commit generation, so that the list of tlog files is
// in synch with the latest index commit point
long startVersion = Math.abs(Long.parseLong(fileName.substring(fileName.lastIndexOf('.') + 1)));
if (startVersion < maxVersion) {
Map<String, Object> fileMeta = new HashMap<>();
fileMeta.put(NAME, fileName);
fileMeta.put(SIZE, new File(ulog.getLogDir(), fileName).length());
tlogFiles.add(fileMeta);
}
}
return tlogFiles;
}
/**
* Retrieves the maximum version number from an index commit.
* NOTE: The commit <b>MUST</b> be reserved before calling this method
@ -1873,8 +1836,6 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
public static final String CONF_FILES = "confFiles";
public static final String TLOG_FILES = "tlogFiles";
public static final String REPLICATE_AFTER = "replicateAfter";
public static final String FILE_STREAM = "filestream";

View File

@ -82,7 +82,6 @@ import org.apache.solr.search.SolrDocumentFetcher;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.SolrReturnFields;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.update.CdcrUpdateLog;
import org.apache.solr.update.DocumentBuilder;
import org.apache.solr.update.IndexFingerprint;
import org.apache.solr.update.PeerSync;
@ -269,11 +268,7 @@ public class RealTimeGetComponent extends SearchComponent
if (oper == UpdateLog.ADD) {
doc = toSolrDoc((SolrInputDocument)entry.get(entry.size()-1), core.getLatestSchema());
} else if (oper == UpdateLog.UPDATE_INPLACE) {
if (ulog instanceof CdcrUpdateLog) {
assert entry.size() == 6;
} else {
assert entry.size() == 5;
}
assert entry.size() == 5;
// For in-place update case, we have obtained the partial document till now. We need to
// resolve it to a full document to be returned to the user.
doc = resolveFullDocument(core, idBytes.get(), rsp.getReturnFields(), (SolrInputDocument)entry.get(entry.size()-1), entry, null);
@ -571,12 +566,8 @@ public class RealTimeGetComponent extends SearchComponent
}
switch (oper) {
case UpdateLog.UPDATE_INPLACE:
if (ulog instanceof CdcrUpdateLog) {
assert entry.size() == 6;
} else {
assert entry.size() == 5;
}
assert entry.size() == 5;
if (resolveFullDocument) {
SolrInputDocument doc = (SolrInputDocument)entry.get(entry.size()-1);
try {

View File

@ -1,401 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.update;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.invoke.MethodHandles;
import java.nio.channels.Channels;
import java.nio.file.Files;
import java.util.Collection;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.util.FastOutputStream;
import org.apache.solr.common.util.JavaBinCodec;
import org.apache.solr.common.util.ObjectReleaseTracker;
import org.apache.solr.update.processor.CdcrUpdateProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Extends {@link org.apache.solr.update.TransactionLog} to:
* <ul>
* <li>reopen automatically the output stream if its reference count reached 0. This is achieved by extending
* methods {@link #incref()}, {@link #close()} and {@link #reopenOutputStream()}.</li>
* <li>encode the number of records in the tlog file in the last commit record. The number of records will be
* decoded and reuse if the tlog file is reopened. This is achieved by extending the constructor, and the
* methods {@link #writeCommit(CommitUpdateCommand)} and {@link #getReader(long)}.</li>
* </ul>
* @deprecated since 8.6
*/
@Deprecated(since = "8.6")
public class CdcrTransactionLog extends TransactionLog {
private boolean isReplaying;
long startVersion; // (absolute) version of the first element of this transaction log
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private boolean debug = log.isDebugEnabled();
CdcrTransactionLog(File tlogFile, Collection<String> globalStrings) {
super(tlogFile, globalStrings);
// The starting version number will be used to seek more efficiently tlogs
// and to filter out tlog files during replication (in ReplicationHandler#getTlogFileList)
String filename = tlogFile.getName();
startVersion = Math.abs(Long.parseLong(filename.substring(filename.lastIndexOf('.') + 1)));
isReplaying = false;
}
CdcrTransactionLog(File tlogFile, Collection<String> globalStrings, boolean openExisting) {
super(tlogFile, globalStrings, openExisting);
// The starting version number will be used to seek more efficiently tlogs
String filename = tlogFile.getName();
startVersion = Math.abs(Long.parseLong(filename.substring(filename.lastIndexOf('.') + 1)));
numRecords = openExisting ? this.readNumRecords() : 0;
// if we try to reopen an existing tlog file and that the number of records is equal to 0, then we are replaying
// the log and we will append a commit
if (openExisting && numRecords == 0) {
isReplaying = true;
}
}
/**
* Returns the number of records in the log (currently includes the header and an optional commit).
*/
public int numRecords() {
return super.numRecords();
}
/**
* The last record of the transaction log file is expected to be a commit with a 4 byte integer that encodes the
* number of records in the file.
*/
private int readNumRecords() {
try {
if (endsWithCommit()) {
long size = fos.size();
// 4 bytes for the record size, the lenght of the end message + 1 byte for its value tag,
// and 4 bytes for the number of records
long pos = size - 4 - END_MESSAGE.length() - 1 - 4;
if (pos < 0) return 0;
try (ChannelFastInputStream is = new ChannelFastInputStream(channel, pos)) {
return is.readInt();
}
}
} catch (IOException e) {
log.error("Error while reading number of records in tlog {}", this, e);
}
return 0;
}
@Override
public long write(AddUpdateCommand cmd, long prevPointer) {
assert (-1 <= prevPointer && (cmd.isInPlaceUpdate() || (-1 == prevPointer)));
LogCodec codec = new LogCodec(resolver);
SolrInputDocument sdoc = cmd.getSolrInputDocument();
try {
checkWriteHeader(codec, sdoc);
// adaptive buffer sizing
int bufSize = lastAddSize; // unsynchronized access of lastAddSize should be fine
bufSize = Math.min(1024*1024, bufSize+(bufSize>>3)+256);
MemOutputStream out = new MemOutputStream(new byte[bufSize]);
codec.init(out);
if (cmd.isInPlaceUpdate()) {
codec.writeTag(JavaBinCodec.ARR, 6);
codec.writeInt(UpdateLog.UPDATE_INPLACE); // should just take one byte
codec.writeLong(cmd.getVersion());
codec.writeLong(prevPointer);
codec.writeLong(cmd.prevVersion);
if (cmd.getReq().getParamString().contains(CdcrUpdateProcessor.CDCR_UPDATE)) {
// if the update is received via cdcr source; add boolean entry
// CdcrReplicator.isTargetCluster() checks that particular boolean to accept or discard the update
// to forward to its own target cluster
codec.writePrimitive(true);
} else {
codec.writePrimitive(false);
}
codec.writeSolrInputDocument(cmd.getSolrInputDocument());
} else {
codec.writeTag(JavaBinCodec.ARR, 4);
codec.writeInt(UpdateLog.ADD); // should just take one byte
codec.writeLong(cmd.getVersion());
if (cmd.getReq().getParamString().contains(CdcrUpdateProcessor.CDCR_UPDATE)) {
// if the update is received via cdcr source; add extra boolean entry
// CdcrReplicator.isTargetCluster() checks that particular boolean to accept or discard the update
// to forward to its own target cluster
codec.writePrimitive(true);
} else {
codec.writePrimitive(false);
}
codec.writeSolrInputDocument(cmd.getSolrInputDocument());
}
lastAddSize = (int)out.size();
synchronized (this) {
long pos = fos.size(); // if we had flushed, this should be equal to channel.position()
assert pos != 0;
/***
System.out.println("###writing at " + pos + " fos.size()=" + fos.size() + " raf.length()=" + raf.length());
if (pos != fos.size()) {
throw new RuntimeException("ERROR" + "###writing at " + pos + " fos.size()=" + fos.size() + " raf.length()=" + raf.length());
}
***/
out.writeAll(fos);
endRecord(pos);
// fos.flushBuffer(); // flush later
return pos;
}
} catch (IOException e) {
// TODO: reset our file pointer back to "pos", the start of this record.
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error logging add", e);
}
}
@Override
public long writeDelete(DeleteUpdateCommand cmd) {
LogCodec codec = new LogCodec(resolver);
try {
checkWriteHeader(codec, null);
BytesRef br = cmd.getIndexedId();
MemOutputStream out = new MemOutputStream(new byte[20 + br.length]);
codec.init(out);
codec.writeTag(JavaBinCodec.ARR, 4);
codec.writeInt(UpdateLog.DELETE); // should just take one byte
codec.writeLong(cmd.getVersion());
codec.writeByteArray(br.bytes, br.offset, br.length);
if (cmd.getReq().getParamString().contains(CdcrUpdateProcessor.CDCR_UPDATE)) {
// if the update is received via cdcr source; add extra boolean entry
// CdcrReplicator.isTargetCluster() checks that particular boolean to accept or discard the update
// to forward to its own target cluster
codec.writePrimitive(true);
} else {
codec.writePrimitive(false);
}
synchronized (this) {
long pos = fos.size(); // if we had flushed, this should be equal to channel.position()
assert pos != 0;
out.writeAll(fos);
endRecord(pos);
// fos.flushBuffer(); // flush later
return pos;
}
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
}
}
@Override
public long writeDeleteByQuery(DeleteUpdateCommand cmd) {
LogCodec codec = new LogCodec(resolver);
try {
checkWriteHeader(codec, null);
MemOutputStream out = new MemOutputStream(new byte[20 + (cmd.query.length())]);
codec.init(out);
codec.writeTag(JavaBinCodec.ARR, 4);
codec.writeInt(UpdateLog.DELETE_BY_QUERY); // should just take one byte
codec.writeLong(cmd.getVersion());
codec.writeStr(cmd.query);
if (cmd.getReq().getParamString().contains(CdcrUpdateProcessor.CDCR_UPDATE)) {
// if the update is received via cdcr source; add extra boolean entry
// CdcrReplicator.isTargetCluster() checks that particular boolean to accept or discard the update
// to forward to its own target cluster
codec.writePrimitive(true);
} else {
codec.writePrimitive(false);
}
synchronized (this) {
long pos = fos.size(); // if we had flushed, this should be equal to channel.position()
out.writeAll(fos);
endRecord(pos);
// fos.flushBuffer(); // flush later
return pos;
}
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
}
}
@Override
public long writeCommit(CommitUpdateCommand cmd) {
LogCodec codec = new LogCodec(resolver);
synchronized (this) {
try {
long pos = fos.size(); // if we had flushed, this should be equal to channel.position()
if (pos == 0) {
writeLogHeader(codec);
pos = fos.size();
}
codec.init(fos);
codec.writeTag(JavaBinCodec.ARR, 4);
codec.writeInt(UpdateLog.COMMIT); // should just take one byte
codec.writeLong(cmd.getVersion());
codec.writeTag(JavaBinCodec.INT); // Enforce the encoding of a plain integer, to simplify decoding
fos.writeInt(numRecords + 1); // the number of records in the file - +1 to account for the commit operation being written
codec.writeStr(END_MESSAGE); // ensure these bytes are (almost) last in the file
endRecord(pos);
fos.flush(); // flush since this will be the last record in a log fill
assert fos.size() == channel.size();
isReplaying = false; // we have replayed and appended a commit record with the number of records in the file
return pos;
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
}
}
}
/**
* Returns a reader that can be used while a log is still in use.
* Currently only *one* LogReader may be outstanding, and that log may only
* be used from a single thread.
*/
@Override
public LogReader getReader(long startingPos) {
return new CdcrLogReader(startingPos);
}
public class CdcrLogReader extends LogReader {
private int numRecords = 1; // start at 1 to account for the header record
public CdcrLogReader(long startingPos) {
super(startingPos);
}
@Override
public Object next() throws IOException, InterruptedException {
Object o = super.next();
if (o != null) {
this.numRecords++;
// We are replaying the log. We need to update the number of records for the writeCommit.
if (isReplaying) {
synchronized (CdcrTransactionLog.this) {
CdcrTransactionLog.this.numRecords = this.numRecords;
}
}
}
return o;
}
}
@Override
public void incref() {
// if the refcount is 0, we need to reopen the output stream
if (refcount.getAndIncrement() == 0) {
reopenOutputStream(); // synchronised with this
}
}
/**
* Modified to act like {@link #incref()} in order to be compatible with {@link UpdateLog#recoverFromLog()}.
* Otherwise, we would have to duplicate the method {@link UpdateLog#recoverFromLog()} in
* {@link org.apache.solr.update.CdcrUpdateLog} and change the call
* {@code if (!ll.try_incref()) continue; } to {@code incref(); }.
*/
@Override
public boolean try_incref() {
this.incref();
return true;
}
@Override
public void close() {
try {
if (debug) {
log.debug("Closing tlog {}", this);
}
synchronized (this) {
if (fos != null) {
fos.flush();
fos.close();
// dereference these variables for GC
fos = null;
os = null;
channel = null;
raf = null;
}
}
if (deleteOnClose) {
try {
Files.deleteIfExists(tlogFile.toPath());
} catch (IOException e) {
// TODO: should this class care if a file couldnt be deleted?
// this just emulates previous behavior, where only SecurityException would be handled.
}
}
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
} finally {
assert ObjectReleaseTracker.release(this);
}
}
/**
* Re-open the output stream of the tlog and position
* the file pointer at the end of the file. It assumes
* that the tlog is non-empty and that the tlog's header
* has been already read.
*/
synchronized void reopenOutputStream() {
try {
if (debug) {
log.debug("Re-opening tlog's output stream: {}", this);
}
raf = new RandomAccessFile(this.tlogFile, "rw");
channel = raf.getChannel();
long start = raf.length();
raf.seek(start);
os = Channels.newOutputStream(channel);
fos = new FastOutputStream(os, new byte[65536], 0);
fos.setWritten(start); // reflect that we aren't starting at the beginning
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
}
}
}

View File

@ -1,796 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.update;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingDeque;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.update.processor.DistributedUpdateProcessor;
import org.apache.solr.update.processor.DistributingUpdateProcessorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An extension of the {@link org.apache.solr.update.UpdateLog} for the CDCR scenario.<br>
* Compared to the original update log implementation, transaction logs are removed based on
* pointers instead of a fixed size limit. Pointers are created by the CDC replicators and
* correspond to replication checkpoints. If all pointers are ahead of a transaction log,
* this transaction log is removed.<br>
* Given that the number of transaction logs can become considerable if some pointers are
* lagging behind, the {@link org.apache.solr.update.CdcrUpdateLog.CdcrLogReader} provides
* a {@link org.apache.solr.update.CdcrUpdateLog.CdcrLogReader#seek(long)} method to
* efficiently lookup a particular transaction log file given a version number.
*/
public class CdcrUpdateLog extends UpdateLog {
protected final Map<CdcrLogReader, CdcrLogPointer> logPointers = new ConcurrentHashMap<>();
/**
* A reader that will be used as toggle to turn on/off the buffering of tlogs
*/
private CdcrLogReader bufferToggle;
public static String LOG_FILENAME_PATTERN = "%s.%019d.%1d";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private boolean debug = log.isDebugEnabled();
@Override
public void init(UpdateHandler uhandler, SolrCore core) {
// remove dangling readers
for (CdcrLogReader reader : logPointers.keySet()) {
reader.close();
}
logPointers.clear();
// init
super.init(uhandler, core);
}
@Override
public TransactionLog newTransactionLog(File tlogFile, Collection<String> globalStrings, boolean openExisting) {
return new CdcrTransactionLog(tlogFile, globalStrings, openExisting);
}
@Override
protected void addOldLog(TransactionLog oldLog, boolean removeOld) {
if (oldLog == null) return;
numOldRecords += oldLog.numRecords();
int currRecords = numOldRecords;
if (oldLog != tlog && tlog != null) {
currRecords += tlog.numRecords();
}
while (removeOld && logs.size() > 0) {
TransactionLog log = logs.peekLast();
int nrec = log.numRecords();
// remove oldest log if we don't need it to keep at least numRecordsToKeep, or if
// we already have the limit of 10 log files.
if (currRecords - nrec >= numRecordsToKeep || logs.size() >= 10) {
// remove the oldest log if nobody points to it
if (!this.hasLogPointer(log)) {
currRecords -= nrec;
numOldRecords -= nrec;
TransactionLog last = logs.removeLast();
last.deleteOnClose = true;
last.close(); // it will be deleted if no longer in use
continue;
}
// we have one log with one pointer, we should stop removing logs
break;
}
break;
}
// Decref old log as we do not write to it anymore
// If the oldlog is uncapped, i.e., a write commit has to be performed
// during recovery, the output stream will be automatically re-open when
// TransaactionLog#incref will be called.
oldLog.deleteOnClose = false;
oldLog.decref();
// don't incref... we are taking ownership from the caller.
logs.addFirst(oldLog);
}
/**
* Checks if one of the log pointer is pointing to the given tlog.
*/
private boolean hasLogPointer(TransactionLog tlog) {
for (CdcrLogPointer pointer : logPointers.values()) {
// if we have a pointer that is not initialised, then do not remove the old tlogs
// as we have a log reader that didn't pick them up yet.
if (!pointer.isInitialised()) {
return true;
}
if (pointer.tlogFile == tlog.tlogFile) {
return true;
}
}
return false;
}
@Override
public long getLastLogId() {
if (id != -1) return id;
if (tlogFiles.length == 0) return -1;
String last = tlogFiles[tlogFiles.length - 1];
if (TLOG_NAME.length() + 1 > last.lastIndexOf('.')) {
// old tlog created by default UpdateLog impl
return Long.parseLong(last.substring(TLOG_NAME.length() + 1));
} else {
return Long.parseLong(last.substring(TLOG_NAME.length() + 1, last.lastIndexOf('.')));
}
}
@Override
public void add(AddUpdateCommand cmd, boolean clearCaches) {
// Ensure we create a new tlog file following our filename format,
// the variable tlog will be not null, and the ensureLog of the parent will be skipped
synchronized (this) {
if ((cmd.getFlags() & UpdateCommand.REPLAY) == 0) {
ensureLog(cmd.getVersion());
}
}
// Then delegate to parent method
super.add(cmd, clearCaches);
}
@Override
public void delete(DeleteUpdateCommand cmd) {
// Ensure we create a new tlog file following our filename format
// the variable tlog will be not null, and the ensureLog of the parent will be skipped
synchronized (this) {
if ((cmd.getFlags() & UpdateCommand.REPLAY) == 0) {
ensureLog(cmd.getVersion());
}
}
// Then delegate to parent method
super.delete(cmd);
}
@Override
public void deleteByQuery(DeleteUpdateCommand cmd) {
// Ensure we create a new tlog file following our filename format
// the variable tlog will be not null, and the ensureLog of the parent will be skipped
synchronized (this) {
if ((cmd.getFlags() & UpdateCommand.REPLAY) == 0) {
ensureLog(cmd.getVersion());
}
}
// Then delegate to parent method
super.deleteByQuery(cmd);
}
/**
* Creates a new {@link org.apache.solr.update.CdcrUpdateLog.CdcrLogReader}
* initialised with the current list of tlogs.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public CdcrLogReader newLogReader() {
return new CdcrLogReader(new ArrayList(logs), tlog);
}
/**
* Enable the buffering of the tlogs. When buffering is activated, the update logs will not remove any
* old transaction log files.
*/
public void enableBuffer() {
if (bufferToggle == null) {
bufferToggle = this.newLogReader();
}
}
/**
* Disable the buffering of the tlogs.
*/
public void disableBuffer() {
if (bufferToggle != null) {
bufferToggle.close();
bufferToggle = null;
}
}
public CdcrLogReader getBufferToggle() {
return bufferToggle;
}
/**
* Is the update log buffering the tlogs ?
*/
public boolean isBuffering() {
return bufferToggle == null ? false : true;
}
protected void ensureLog(long startVersion) {
if (tlog == null) {
long absoluteVersion = Math.abs(startVersion); // version is negative for deletes
if (tlog == null) {
String newLogName = String.format(Locale.ROOT, LOG_FILENAME_PATTERN, TLOG_NAME, id, absoluteVersion);
tlog = new CdcrTransactionLog(new File(tlogDir, newLogName), globalStrings);
}
// push the new tlog to the opened readers
for (CdcrLogReader reader : logPointers.keySet()) {
reader.push(tlog);
}
}
}
/**
* expert: Reset the update log before initialisation. This is called by
* {@link org.apache.solr.handler.IndexFetcher#moveTlogFiles(File)} during a
* a Recovery operation in order to re-initialise the UpdateLog with a new set of tlog files.
* @see #initForRecovery(File, long)
*/
public BufferedUpdates resetForRecovery() {
synchronized (this) { // since we blocked updates in IndexFetcher, this synchronization shouldn't strictly be necessary.
// If we are buffering, we need to return the related information to the index fetcher
// for properly initialising the new update log - SOLR-8263
BufferedUpdates bufferedUpdates = new BufferedUpdates();
if (state == State.BUFFERING && tlog != null) {
bufferedUpdates.tlog = tlog.tlogFile; // file to keep
bufferedUpdates.offset = this.recoveryInfo.positionOfStart;
}
// Close readers
for (CdcrLogReader reader : logPointers.keySet()) {
reader.close();
}
logPointers.clear();
// Close and clear logs
doClose(prevTlog);
doClose(tlog);
for (TransactionLog log : logs) {
if (log == prevTlog || log == tlog) continue;
doClose(log);
}
logs.clear();
newestLogsOnStartup.clear();
tlog = prevTlog = null;
prevMapLog = prevMapLog2 = null;
map.clear();
if (prevMap != null) prevMap.clear();
if (prevMap2 != null) prevMap2.clear();
tlogFiles = null;
numOldRecords = 0;
oldDeletes.clear();
deleteByQueries.clear();
return bufferedUpdates;
}
}
public static class BufferedUpdates {
public File tlog;
public long offset;
}
/**
* <p>
* expert: Initialise the update log with a tlog file containing buffered updates. This is called by
* {@link org.apache.solr.handler.IndexFetcher#moveTlogFiles(File)} during a Recovery operation.
* This is mainly a copy of the original {@link UpdateLog#init(UpdateHandler, SolrCore)} method, but modified
* to:
* <ul>
* <li>preserve the same {@link VersionInfo} instance in order to not "unblock" updates, since the
* {@link org.apache.solr.handler.IndexFetcher#moveTlogFiles(File)} acquired a write lock from this instance.</li>
* <li>copy the buffered updates.</li>
* </ul>
* @see #resetForRecovery()
*/
public void initForRecovery(File bufferedTlog, long offset) {
tlogFiles = getLogList(tlogDir);
id = getLastLogId() + 1; // add 1 since we will create a new log for the next update
if (debug) {
log.debug("UpdateHandler init: tlogDir={}, existing tlogs={}, next id={}", tlogDir, Arrays.asList(tlogFiles), id);
}
TransactionLog oldLog = null;
for (String oldLogName : tlogFiles) {
File f = new File(tlogDir, oldLogName);
try {
oldLog = newTransactionLog(f, null, true);
addOldLog(oldLog, false); // don't remove old logs on startup since more than one may be uncapped.
} catch (Exception e) {
SolrException.log(log, "Failure to open existing log file (non fatal) " + f, e);
deleteFile(f);
}
}
// Record first two logs (oldest first) at startup for potential tlog recovery.
// It's possible that at abnormal close both "tlog" and "prevTlog" were uncapped.
for (TransactionLog ll : logs) {
newestLogsOnStartup.addFirst(ll);
if (newestLogsOnStartup.size() >= 2) break;
}
// TODO: these startingVersions assume that we successfully recover from all non-complete tlogs.
UpdateLog.RecentUpdates startingUpdates = getRecentUpdates();
long latestVersion = startingUpdates.getMaxRecentVersion();
try {
startingVersions = startingUpdates.getVersions(numRecordsToKeep);
// populate recent deletes list (since we can't get that info from the index)
for (int i=startingUpdates.deleteList.size()-1; i>=0; i--) {
DeleteUpdate du = startingUpdates.deleteList.get(i);
oldDeletes.put(new BytesRef(du.id), new LogPtr(-1,du.version));
}
// populate recent deleteByQuery commands
for (int i=startingUpdates.deleteByQueryList.size()-1; i>=0; i--) {
Update update = startingUpdates.deleteByQueryList.get(i);
@SuppressWarnings({"unchecked"})
List<Object> dbq = (List<Object>) update.log.lookup(update.pointer);
long version = (Long) dbq.get(1);
String q = (String) dbq.get(2);
trackDeleteByQuery(q, version);
}
} finally {
startingUpdates.close();
}
// Copy buffered updates
if (bufferedTlog != null) {
this.copyBufferedUpdates(bufferedTlog, offset, latestVersion);
}
}
/**
* <p>
* Read the entries from the given tlog file and replay them as buffered updates.
* The buffered tlog that we are trying to copy might contain duplicate operations with the
* current update log. During the tlog replication process, the replica might buffer update operations
* that will be present also in the tlog files downloaded from the leader. In order to remove these
* duplicates, it will skip any operations with a version inferior to the latest know version.
*/
private void copyBufferedUpdates(File tlogSrc, long offsetSrc, long latestVersion) {
recoveryInfo = new RecoveryInfo();
state = State.BUFFERING;
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(DistributingUpdateProcessorFactory.DISTRIB_UPDATE_PARAM, DistributedUpdateProcessor.DistribPhase.FROMLEADER.toString());
SolrQueryRequest req = new LocalSolrQueryRequest(uhandler.core, params);
CdcrTransactionLog src = new CdcrTransactionLog(tlogSrc, null, true);
TransactionLog.LogReader tlogReader = src.getReader(offsetSrc);
try {
int operationAndFlags = 0;
for (; ; ) {
Object o = tlogReader.next();
if (o == null) break; // we reached the end of the tlog
// should currently be a List<Oper,Ver,Doc/Id>
@SuppressWarnings({"rawtypes"})
List entry = (List) o;
operationAndFlags = (Integer) entry.get(0);
int oper = operationAndFlags & OPERATION_MASK;
long version = (Long) entry.get(1);
if (version <= latestVersion) {
// probably a buffered update that is also present in a tlog file coming from the leader,
// skip it.
log.debug("Dropping buffered operation - version {} < {}", version, latestVersion);
continue;
}
switch (oper) {
case UpdateLog.ADD: {
SolrInputDocument sdoc = (SolrInputDocument) entry.get(entry.size() - 1);
AddUpdateCommand cmd = new AddUpdateCommand(req);
cmd.solrDoc = sdoc;
cmd.setVersion(version);
cmd.setFlags(UpdateCommand.BUFFERING);
this.add(cmd);
break;
}
case UpdateLog.DELETE: {
byte[] idBytes = (byte[]) entry.get(2);
DeleteUpdateCommand cmd = new DeleteUpdateCommand(req);
cmd.setIndexedId(new BytesRef(idBytes));
cmd.setVersion(version);
cmd.setFlags(UpdateCommand.BUFFERING);
this.delete(cmd);
break;
}
case UpdateLog.DELETE_BY_QUERY: {
String query = (String) entry.get(2);
DeleteUpdateCommand cmd = new DeleteUpdateCommand(req);
cmd.query = query;
cmd.setVersion(version);
cmd.setFlags(UpdateCommand.BUFFERING);
this.deleteByQuery(cmd);
break;
}
default:
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Invalid Operation! " + oper);
}
}
}
catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to copy buffered updates", e);
}
finally {
try {
tlogReader.close();
}
finally {
this.doClose(src);
}
}
}
private void doClose(TransactionLog theLog) {
if (theLog != null) {
theLog.deleteOnClose = false;
theLog.decref();
theLog.forceClose();
}
}
@Override
public void close(boolean committed, boolean deleteOnClose) {
for (CdcrLogReader reader : new ArrayList<>(logPointers.keySet())) {
reader.close();
}
super.close(committed, deleteOnClose);
}
private static class CdcrLogPointer {
File tlogFile = null;
private CdcrLogPointer() {
}
private void set(File tlogFile) {
this.tlogFile = tlogFile;
}
private boolean isInitialised() {
return tlogFile == null ? false : true;
}
@Override
public String toString() {
return "CdcrLogPointer(" + tlogFile + ")";
}
}
public class CdcrLogReader {
private TransactionLog currentTlog;
private TransactionLog.LogReader tlogReader;
// we need to use a blocking deque because of #getNumberOfRemainingRecords
private final LinkedBlockingDeque<TransactionLog> tlogs;
private final CdcrLogPointer pointer;
/**
* Used to record the last position of the tlog
*/
private long lastPositionInTLog = 0;
/**
* lastVersion is used to get nextToLastVersion
*/
private long lastVersion = -1;
/**
* nextToLastVersion is communicated by leader to replicas so that they can remove no longer needed tlogs
* <p>
* nextToLastVersion is used because thanks to {@link #resetToLastPosition()} lastVersion can become the current version
*/
private long nextToLastVersion = -1;
/**
* Used to record the number of records read in the current tlog
*/
private long numRecordsReadInCurrentTlog = 0;
private CdcrLogReader(List<TransactionLog> tlogs, TransactionLog tlog) {
this.tlogs = new LinkedBlockingDeque<>();
this.tlogs.addAll(tlogs);
if (tlog != null) this.tlogs.push(tlog); // ensure that the tlog being written is pushed
// Register the pointer in the parent UpdateLog
pointer = new CdcrLogPointer();
logPointers.put(this, pointer);
// If the reader is initialised while the updates log is empty, do nothing
if ((currentTlog = this.tlogs.peekLast()) != null) {
tlogReader = currentTlog.getReader(0);
pointer.set(currentTlog.tlogFile);
numRecordsReadInCurrentTlog = 0;
log.debug("Init new tlog reader for {} - tlogReader = {}", currentTlog.tlogFile, tlogReader);
}
}
private void push(TransactionLog tlog) {
this.tlogs.push(tlog);
// The reader was initialised while the update logs was empty, or reader was exhausted previously,
// we have to update the current tlog and the associated tlog reader.
if (currentTlog == null && !tlogs.isEmpty()) {
currentTlog = tlogs.peekLast();
tlogReader = currentTlog.getReader(0);
pointer.set(currentTlog.tlogFile);
numRecordsReadInCurrentTlog = 0;
log.debug("Init new tlog reader for {} - tlogReader = {}", currentTlog.tlogFile, tlogReader);
}
}
/**
* Expert: Instantiate a sub-reader. A sub-reader is used for batch updates. It allows to iterates over the
* update logs entries without modifying the state of the parent log reader. If the batch update fails, the state
* of the sub-reader is discarded and the state of the parent reader is not modified. If the batch update
* is successful, the sub-reader is used to fast forward the parent reader with the method
* {@link #forwardSeek(org.apache.solr.update.CdcrUpdateLog.CdcrLogReader)}.
*/
public CdcrLogReader getSubReader() {
// Add the last element of the queue to properly initialise the pointer and log reader
CdcrLogReader clone = new CdcrLogReader(new ArrayList<TransactionLog>(), this.tlogs.peekLast());
clone.tlogs.clear(); // clear queue before copy
clone.tlogs.addAll(tlogs); // perform a copy of the list
clone.lastPositionInTLog = this.lastPositionInTLog;
clone.numRecordsReadInCurrentTlog = this.numRecordsReadInCurrentTlog;
clone.lastVersion = this.lastVersion;
clone.nextToLastVersion = this.nextToLastVersion;
// If the update log is not empty, we need to initialise the tlog reader
// NB: the tlogReader is equal to null if the update log is empty
if (tlogReader != null) {
clone.tlogReader.close();
clone.tlogReader = currentTlog.getReader(this.tlogReader.currentPos());
}
return clone;
}
/**
* Expert: Fast forward this log reader with a log subreader. The subreader will be closed after calling this
* method. In order to avoid unexpected results, the log
* subreader must be created from this reader with the method {@link #getSubReader()}.
*/
public void forwardSeek(CdcrLogReader subReader) {
// If a subreader has a null tlog reader, does nothing
// This can happened if a subreader is instantiated from a non-initialised parent reader, or if the subreader
// has been closed.
if (subReader.tlogReader == null) {
return;
}
tlogReader.close(); // close the existing reader, a new one will be created
while (this.tlogs.peekLast().id < subReader.tlogs.peekLast().id) {
tlogs.removeLast();
currentTlog = tlogs.peekLast();
}
assert this.tlogs.peekLast().id == subReader.tlogs.peekLast().id : this.tlogs.peekLast().id+" != "+subReader.tlogs.peekLast().id;
this.pointer.set(currentTlog.tlogFile);
this.lastPositionInTLog = subReader.lastPositionInTLog;
this.numRecordsReadInCurrentTlog = subReader.numRecordsReadInCurrentTlog;
this.lastVersion = subReader.lastVersion;
this.nextToLastVersion = subReader.nextToLastVersion;
this.tlogReader = currentTlog.getReader(subReader.tlogReader.currentPos());
}
/**
* Advances to the next log entry in the updates log and returns the log entry itself.
* Returns null if there are no more log entries in the updates log.<br>
* <p>
* <b>NOTE:</b> after the reader has exhausted, you can call again this method since the updates
* log might have been updated with new entries.
*/
public Object next() throws IOException, InterruptedException {
while (!tlogs.isEmpty()) {
lastPositionInTLog = tlogReader.currentPos();
Object o = tlogReader.next();
if (o != null) {
pointer.set(currentTlog.tlogFile);
nextToLastVersion = lastVersion;
lastVersion = getVersion(o);
numRecordsReadInCurrentTlog++;
return o;
}
if (tlogs.size() > 1) { // if the current tlog is not the newest one, we can advance to the next one
tlogReader.close();
tlogs.removeLast();
currentTlog = tlogs.peekLast();
tlogReader = currentTlog.getReader(0);
pointer.set(currentTlog.tlogFile);
numRecordsReadInCurrentTlog = 0;
log.debug("Init new tlog reader for {} - tlogReader = {}", currentTlog.tlogFile, tlogReader);
} else {
// the only tlog left is the new tlog which is currently being written,
// we should not remove it as we have to try to read it again later.
return null;
}
}
return null;
}
/**
* Advances to the first beyond the current whose version number is greater
* than or equal to <i>targetVersion</i>.<br>
* Returns true if the reader has been advanced. If <i>targetVersion</i> is
* greater than the highest version number in the updates log, the reader
* has been advanced to the end of the current tlog, and a call to
* {@link #next()} will probably return null.<br>
* Returns false if <i>targetVersion</i> is lower than the oldest known entry.
* In this scenario, it probably means that there is a gap in the updates log.<br>
* <p>
* <b>NOTE:</b> This method must be called before the first call to {@link #next()}.
*/
public boolean seek(long targetVersion) throws IOException, InterruptedException {
Object o;
// version is negative for deletes - ensure that we are manipulating absolute version numbers.
targetVersion = Math.abs(targetVersion);
if (tlogs.isEmpty() || !this.seekTLog(targetVersion)) {
return false;
}
// now that we might be on the right tlog, iterates over the entries to find the one we are looking for
while ((o = this.next()) != null) {
if (this.getVersion(o) >= targetVersion) {
this.resetToLastPosition();
return true;
}
}
return true;
}
/**
* Seeks the tlog associated to the target version by using the updates log index,
* and initialises the log reader to the start of the tlog. Returns true if it was able
* to seek the corresponding tlog, false if the <i>targetVersion</i> is lower than the
* oldest known entry (which probably indicates a gap).<br>
* <p>
* <b>NOTE:</b> This method might modify the tlog queue by removing tlogs that are older
* than the target version.
*/
private boolean seekTLog(long targetVersion) {
// if the target version is lower than the oldest known entry, we have probably a gap.
if (targetVersion < ((CdcrTransactionLog) tlogs.peekLast()).startVersion) {
return false;
}
// closes existing reader before performing seek and possibly modifying the queue;
tlogReader.close();
// iterates over the queue and removes old tlogs
TransactionLog last = null;
while (tlogs.size() > 1) {
if (((CdcrTransactionLog) tlogs.peekLast()).startVersion >= targetVersion) {
break;
}
last = tlogs.pollLast();
}
// the last tlog removed is the one we look for, add it back to the queue
if (last != null) tlogs.addLast(last);
currentTlog = tlogs.peekLast();
tlogReader = currentTlog.getReader(0);
pointer.set(currentTlog.tlogFile);
numRecordsReadInCurrentTlog = 0;
return true;
}
/**
* Extracts the version number and converts it to its absolute form.
*/
private long getVersion(Object o) {
@SuppressWarnings({"rawtypes"})
List entry = (List) o;
// version is negative for delete, ensure that we are manipulating absolute version numbers
return Math.abs((Long) entry.get(1));
}
/**
* If called after {@link #next()}, it resets the reader to its last position.
*/
public void resetToLastPosition() {
try {
if (tlogReader != null) {
tlogReader.fis.seek(lastPositionInTLog);
numRecordsReadInCurrentTlog--;
lastVersion = nextToLastVersion;
}
} catch (IOException e) {
log.error("Failed to seek last position in tlog", e);
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Failed to seek last position in tlog", e);
}
}
/**
* Returns the number of remaining records (including commit but excluding header) to be read in the logs.
*/
public long getNumberOfRemainingRecords() {
long numRemainingRecords = 0;
synchronized (tlogs) {
for (TransactionLog tlog : tlogs) {
numRemainingRecords += tlog.numRecords() - 1; // minus 1 as the number of records returned by the tlog includes the header
}
}
return numRemainingRecords - numRecordsReadInCurrentTlog;
}
/**
* Closes streams and remove the associated {@link org.apache.solr.update.CdcrUpdateLog.CdcrLogPointer} from the
* parent {@link org.apache.solr.update.CdcrUpdateLog}.
*/
public void close() {
if (tlogReader != null) {
tlogReader.close();
tlogReader = null;
currentTlog = null;
}
tlogs.clear();
logPointers.remove(this);
}
/**
* Returns the absolute form of the version number of the last entry read. If the current version is equal
* to 0 (because of a commit), it will return the next to last version number.
*/
public long getLastVersion() {
return lastVersion == 0 ? nextToLastVersion : lastVersion;
}
}
}

View File

@ -18,12 +18,10 @@ package org.apache.solr.update;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -79,14 +77,6 @@ public final class DefaultSolrCoreState extends SolrCoreState implements Recover
protected final ReentrantLock commitLock = new ReentrantLock();
private AtomicBoolean cdcrRunning = new AtomicBoolean();
private volatile Future<Boolean> cdcrBootstrapFuture;
@SuppressWarnings({"rawtypes"})
private volatile Callable cdcrBootstrapCallable;
@Deprecated
public DefaultSolrCoreState(DirectoryFactory directoryFactory) {
this(directoryFactory, new RecoveryStrategy.Builder());
@ -427,35 +417,4 @@ public final class DefaultSolrCoreState extends SolrCoreState implements Recover
public Lock getRecoveryLock() {
return recoveryLock;
}
@Override
public boolean getCdcrBootstrapRunning() {
return cdcrRunning.get();
}
@Override
public void setCdcrBootstrapRunning(boolean cdcrRunning) {
this.cdcrRunning.set(cdcrRunning);
}
@Override
public Future<Boolean> getCdcrBootstrapFuture() {
return cdcrBootstrapFuture;
}
@Override
public void setCdcrBootstrapFuture(Future<Boolean> cdcrBootstrapFuture) {
this.cdcrBootstrapFuture = cdcrBootstrapFuture;
}
@Override
@SuppressWarnings({"rawtypes"})
public Callable getCdcrBootstrapCallable() {
return cdcrBootstrapCallable;
}
@Override
public void setCdcrBootstrapCallable(@SuppressWarnings({"rawtypes"})Callable cdcrBootstrapCallable) {
this.cdcrBootstrapCallable = cdcrBootstrapCallable;
}
}

View File

@ -18,8 +18,6 @@ package org.apache.solr.update;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import org.apache.lucene.index.IndexWriter;
@ -186,21 +184,6 @@ public abstract class SolrCoreState {
public abstract Lock getRecoveryLock();
// These are needed to properly synchronize the bootstrapping when the
// in the target DC require a full sync.
public abstract boolean getCdcrBootstrapRunning();
public abstract void setCdcrBootstrapRunning(boolean cdcrRunning);
public abstract Future<Boolean> getCdcrBootstrapFuture();
public abstract void setCdcrBootstrapFuture(Future<Boolean> cdcrBootstrapFuture);
@SuppressWarnings("rawtypes")
public abstract Callable getCdcrBootstrapCallable();
public abstract void setCdcrBootstrapCallable(@SuppressWarnings("rawtypes") Callable cdcrBootstrapCallable);
public Throwable getTragicException() throws IOException {
RefCounted<IndexWriter> ref = getIndexWriter(null);
if (ref == null) return null;

View File

@ -1517,8 +1517,7 @@ public class UpdateLog implements PluginInfoInitialized, SolrMetricProducer {
update.version = version;
if (oper == UpdateLog.UPDATE_INPLACE) {
if ((update.log instanceof CdcrTransactionLog && entry.size() == 6) ||
(!(update.log instanceof CdcrTransactionLog) && entry.size() == 5)) {
if (entry.size() == 5) {
update.previousVersion = (Long) entry.get(UpdateLog.PREV_VERSION_IDX);
}
}

View File

@ -1,132 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.update.processor;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.update.AddUpdateCommand;
import org.apache.solr.update.DeleteUpdateCommand;
import org.apache.solr.update.UpdateCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Extends {@link org.apache.solr.update.processor.DistributedUpdateProcessor} to force peer sync logic
* for every updates. This ensures that the version parameter sent by the source cluster is kept
* by the target cluster.
* </p>
* @deprecated since 8.6
*/
@Deprecated(since = "8.6")
public class CdcrUpdateProcessor extends DistributedZkUpdateProcessor {
public static final String CDCR_UPDATE = "cdcr.update";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public CdcrUpdateProcessor(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor next) {
super(req, rsp, next);
}
@Override
protected boolean versionAdd(AddUpdateCommand cmd) throws IOException {
/*
temporarily set the PEER_SYNC flag so that DistributedUpdateProcessor.versionAdd doesn't execute leader logic
but the else part of that if. That way version remains preserved.
we cannot set the flag for the whole processAdd method because DistributedUpdateProcessor.setupRequest() would set
isLeader to false which wouldn't work
*/
if (cmd.getReq().getParams().get(CDCR_UPDATE) != null) {
cmd.setFlags(cmd.getFlags() | UpdateCommand.PEER_SYNC); // we need super.versionAdd() to set leaderLogic to false
}
boolean result = super.versionAdd(cmd);
// unset the flag to avoid unintended consequences down the chain
if (cmd.getReq().getParams().get(CDCR_UPDATE) != null) {
cmd.setFlags(cmd.getFlags() & ~UpdateCommand.PEER_SYNC);
}
return result;
}
@Override
protected boolean versionDelete(DeleteUpdateCommand cmd) throws IOException {
/*
temporarily set the PEER_SYNC flag so that DistributedUpdateProcessor.deleteAdd doesn't execute leader logic
but the else part of that if. That way version remains preserved.
we cannot set the flag for the whole processDelete method because DistributedUpdateProcessor.setupRequest() would set
isLeader to false which wouldn't work
*/
if (cmd.getReq().getParams().get(CDCR_UPDATE) != null) {
cmd.setFlags(cmd.getFlags() | UpdateCommand.PEER_SYNC); // we need super.versionAdd() to set leaderLogic to false
}
boolean result = super.versionDelete(cmd);
// unset the flag to avoid unintended consequences down the chain
if (cmd.getReq().getParams().get(CDCR_UPDATE) != null) {
cmd.setFlags(cmd.getFlags() & ~UpdateCommand.PEER_SYNC);
}
return result;
}
protected ModifiableSolrParams filterParams(SolrParams params) {
ModifiableSolrParams result = super.filterParams(params);
if (params.get(CDCR_UPDATE) != null) {
result.set(CDCR_UPDATE, "");
// if (params.get(DistributedUpdateProcessor.VERSION_FIELD) == null) {
// log.warn("+++ cdcr.update but no version field, params are: " + params);
// } else {
// log.info("+++ cdcr.update version present, params are: " + params);
// }
result.set(CommonParams.VERSION_FIELD, params.get(CommonParams.VERSION_FIELD));
}
return result;
}
@Override
protected void versionDeleteByQuery(DeleteUpdateCommand cmd) throws IOException {
/*
temporarily set the PEER_SYNC flag so that DistributedUpdateProcessor.versionDeleteByQuery doesn't execute leader logic
That way version remains preserved.
*/
if (cmd.getReq().getParams().get(CDCR_UPDATE) != null) {
cmd.setFlags(cmd.getFlags() | UpdateCommand.PEER_SYNC); // we need super.versionDeleteByQuery() to set leaderLogic to false
}
super.versionDeleteByQuery(cmd);
// unset the flag to avoid unintended consequences down the chain
if (cmd.getReq().getParams().get(CDCR_UPDATE) != null) {
cmd.setFlags(cmd.getFlags() & ~UpdateCommand.PEER_SYNC);
}
}
}

View File

@ -1,46 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.update.processor;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
/**
* Factory for {@link org.apache.solr.update.processor.CdcrUpdateProcessor}.
*
* @see org.apache.solr.update.processor.CdcrUpdateProcessor
* @since 6.0.0
*/
public class CdcrUpdateProcessorFactory
extends UpdateRequestProcessorFactory
implements DistributingUpdateProcessorFactory {
@Override
public void init(@SuppressWarnings({"rawtypes"})NamedList args) {
}
@Override
public CdcrUpdateProcessor getInstance(SolrQueryRequest req,
SolrQueryResponse rsp, UpdateRequestProcessor next) {
return new CdcrUpdateProcessor(req, rsp, next);
}
}

View File

@ -1,77 +0,0 @@
<?xml version="1.0" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<config>
<jmx/>
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
<directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.RAMDirectoryFactory}">
<!-- used to keep RAM reqs down for HdfsDirectoryFactory -->
<bool name="solr.hdfs.blockcache.enabled">${solr.hdfs.blockcache.enabled:true}</bool>
<int name="solr.hdfs.blockcache.blocksperbank">${solr.hdfs.blockcache.blocksperbank:1024}</int>
<str name="solr.hdfs.home">${solr.hdfs.home:}</str>
<str name="solr.hdfs.confdir">${solr.hdfs.confdir:}</str>
<str name="solr.hdfs.blockcache.global">${solr.hdfs.blockcache.global:false}</str>
</directoryFactory>
<schemaFactory class="ClassicIndexSchemaFactory"/>
<dataDir>${solr.data.dir:}</dataDir>
<xi:include href="solrconfig.snippet.randomindexconfig.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
<requestHandler name="/select" class="solr.SearchHandler">
</requestHandler>
<requestHandler name="/update" class="solr.UpdateRequestHandler">
<lst name="defaults">
<str name="update.chain">cdcr-processor-chain</str>
</lst>
</requestHandler>
<updateRequestProcessorChain name="cdcr-processor-chain">
<processor class="solr.CdcrUpdateProcessorFactory"/>
<processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>
<requestHandler name="/cdcr" class="solr.CdcrRequestHandler">
<lst name="replica">
<str name="zkHost">${zkHost}</str>
<str name="source">source_collection</str>
<str name="target">target_collection</str>
</lst>
<lst name="replicator">
<str name="threadPoolSize">8</str>
<str name="schedule">1000</str>
<str name="batchSize">64</str>
</lst>
<lst name="updateLogSynchronizer">
<str name="schedule">1000</str>
</lst>
</requestHandler>
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
</updateLog>
</updateHandler>
</config>

View File

@ -1,49 +0,0 @@
<?xml version="1.0" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<config>
<jmx/>
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
<directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.RAMDirectoryFactory}">
<!-- used to keep RAM reqs down for HdfsDirectoryFactory -->
<bool name="solr.hdfs.blockcache.enabled">${solr.hdfs.blockcache.enabled:true}</bool>
<int name="solr.hdfs.blockcache.blocksperbank">${solr.hdfs.blockcache.blocksperbank:1024}</int>
<str name="solr.hdfs.home">${solr.hdfs.home:}</str>
<str name="solr.hdfs.confdir">${solr.hdfs.confdir:}</str>
<str name="solr.hdfs.blockcache.global">${solr.hdfs.blockcache.global:false}</str>
</directoryFactory>
<schemaFactory class="ClassicIndexSchemaFactory"/>
<dataDir>${solr.data.dir:}</dataDir>
<xi:include href="solrconfig.snippet.randomindexconfig.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
<requestHandler name="/select" class="solr.SearchHandler">
</requestHandler>
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
</updateLog>
</updateHandler>
</config>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<schema name="minimal" version="1.1">
<types>
<fieldType name="string" class="solr.StrField"/>
<fieldType name="long" class="${solr.tests.LongFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" positionIncrementGap="0"/>
</types>
<fields>
<field name="id" type="string" indexed="true" stored="true"/>
<field name="_version_" type="long" indexed="true" stored="true"/>
<dynamicField name="*" type="string" indexed="true" stored="true"/>
</fields>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -1,80 +0,0 @@
<?xml version="1.0" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<!-- This is a "kitchen sink" config file that tests can use.
When writting a new test, feel free to add *new* items (plugins,
config options, etc...) as long as they don't break any existing
tests. if you need to test something esoteric please add a new
"solrconfig-your-esoteric-purpose.xml" config file.
Note in particular that this test is used by MinimalSchemaTest so
Anything added to this file needs to work correctly even if there
is now uniqueKey or defaultSearch Field.
-->
<config>
<dataDir>${solr.data.dir:}</dataDir>
<directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
<updateRequestProcessorChain name="cdcr-processor-chain">
<processor class="solr.CdcrUpdateProcessorFactory"/>
<processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>
<requestHandler name="/cdcr" class="solr.CdcrRequestHandler">
<lst name="replica">
<str name="zkHost">${cdcr.cluster2.zkHost}</str>
<str name="source">cdcr-cluster1</str>
<str name="target">cdcr-cluster2</str>
</lst>
<lst name="replicator">
<str name="threadPoolSize">1</str>
<str name="schedule">1000</str>
<str name="batchSize">1000</str>
</lst>
<lst name="updateLogSynchronizer">
<str name="schedule">1000</str>
</lst>
</requestHandler>
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
</updateLog>
</updateHandler>
<requestHandler name="/select" class="solr.SearchHandler" />
<initParams path="/update/**,/query,/select,/tvrh,/elevate,/spell,/browse">
<lst name="defaults">
<str name="df">_text_</str>
</lst>
</initParams>
<requestHandler name="/update" class="solr.UpdateRequestHandler">
<lst name="defaults">
<str name="update.chain">cdcr-processor-chain</str>
</lst>
</requestHandler>
</config>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<schema name="minimal" version="1.1">
<types>
<fieldType name="string" class="solr.StrField"/>
<fieldType name="long" class="${solr.tests.LongFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" positionIncrementGap="0"/>
</types>
<fields>
<field name="id" type="string" indexed="true" stored="true"/>
<field name="_version_" type="long" indexed="true" stored="true"/>
<dynamicField name="*" type="string" indexed="true" stored="true"/>
</fields>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -1,80 +0,0 @@
<?xml version="1.0" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<!-- This is a "kitchen sink" config file that tests can use.
When writting a new test, feel free to add *new* items (plugins,
config options, etc...) as long as they don't break any existing
tests. if you need to test something esoteric please add a new
"solrconfig-your-esoteric-purpose.xml" config file.
Note in particular that this test is used by MinimalSchemaTest so
Anything added to this file needs to work correctly even if there
is now uniqueKey or defaultSearch Field.
-->
<config>
<dataDir>${solr.data.dir:}</dataDir>
<directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
<updateRequestProcessorChain name="cdcr-processor-chain">
<processor class="solr.CdcrUpdateProcessorFactory"/>
<processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>
<requestHandler name="/cdcr" class="solr.CdcrRequestHandler">
<lst name="replica">
<str name="zkHost">${cdcr.cluster1.zkHost}</str>
<str name="source">cdcr-cluster2</str>
<str name="target">cdcr-cluster1</str>
</lst>
<lst name="replicator">
<str name="threadPoolSize">1</str>
<str name="schedule">1000</str>
<str name="batchSize">1000</str>
</lst>
<lst name="updateLogSynchronizer">
<str name="schedule">1000</str>
</lst>
</requestHandler>
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
</updateLog>
</updateHandler>
<requestHandler name="/select" class="solr.SearchHandler" />
<initParams path="/update/**,/query,/select,/tvrh,/elevate,/spell,/browse">
<lst name="defaults">
<str name="df">_text_</str>
</lst>
</initParams>
<requestHandler name="/update" class="solr.UpdateRequestHandler">
<lst name="defaults">
<str name="update.chain">cdcr-processor-chain</str>
</lst>
</requestHandler>
</config>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<schema name="minimal" version="1.1">
<types>
<fieldType name="string" class="solr.StrField"/>
<fieldType name="long" class="${solr.tests.LongFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" positionIncrementGap="0"/>
</types>
<fields>
<field name="id" type="string" indexed="true" stored="true"/>
<field name="_version_" type="long" indexed="true" stored="true"/>
<dynamicField name="*" type="string" indexed="true" stored="true"/>
</fields>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -1,60 +0,0 @@
<?xml version="1.0" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<!-- This is a "kitchen sink" config file that tests can use.
When writting a new test, feel free to add *new* items (plugins,
config options, etc...) as long as they don't break any existing
tests. if you need to test something esoteric please add a new
"solrconfig-your-esoteric-purpose.xml" config file.
Note in particular that this test is used by MinimalSchemaTest so
Anything added to this file needs to work correctly even if there
is now uniqueKey or defaultSearch Field.
-->
<config>
<dataDir>${solr.data.dir:}</dataDir>
<directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
<schemaFactory class="ClassicIndexSchemaFactory"/>
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
<updateHandler class="solr.DirectUpdateHandler2">
<commitWithin>
<softCommit>${solr.commitwithin.softcommit:true}</softCommit>
</commitWithin>
<updateLog>
<str name="dir">${solr.ulog.dir:}</str>
</updateLog>
</updateHandler>
<requestHandler name="/select" class="solr.SearchHandler">
<lst name="defaults">
<str name="echoParams">explicit</str>
<str name="indent">true</str>
<str name="df">text</str>
</lst>
</requestHandler>
</config>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<schema name="minimal" version="1.1">
<types>
<fieldType name="string" class="solr.StrField"/>
<fieldType name="long" class="${solr.tests.LongFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" positionIncrementGap="0"/>
</types>
<fields>
<field name="id" type="string" indexed="true" stored="true"/>
<field name="_version_" type="long" indexed="true" stored="true"/>
<dynamicField name="*" type="string" indexed="true" stored="true"/>
</fields>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -1,75 +0,0 @@
<?xml version="1.0" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<!-- This is a "kitchen sink" config file that tests can use.
When writting a new test, feel free to add *new* items (plugins,
config options, etc...) as long as they don't break any existing
tests. if you need to test something esoteric please add a new
"solrconfig-your-esoteric-purpose.xml" config file.
Note in particular that this test is used by MinimalSchemaTest so
Anything added to this file needs to work correctly even if there
is now uniqueKey or defaultSearch Field.
-->
<config>
<dataDir>${solr.data.dir:}</dataDir>
<directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
<updateRequestProcessorChain name="cdcr-processor-chain">
<processor class="solr.CdcrUpdateProcessorFactory"/>
<processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>
<requestHandler name="/cdcr" class="solr.CdcrRequestHandler">
<lst name="replica">
<str name="zkHost">${cdcr.target.zkHost}</str>
<str name="source">cdcr-source</str>
<str name="target">cdcr-target</str>
</lst>
<lst name="replicator">
<str name="threadPoolSize">1</str>
<str name="schedule">1000</str>
<str name="batchSize">1000</str>
</lst>
<lst name="updateLogSynchronizer">
<str name="schedule">1000</str>
</lst>
</requestHandler>
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
</updateLog>
</updateHandler>
<requestHandler name="/select" class="solr.SearchHandler" />
<requestHandler name="/update" class="solr.UpdateRequestHandler">
<lst name="defaults">
<str name="update.chain">cdcr-processor-chain</str>
</lst>
</requestHandler>
</config>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<schema name="minimal" version="1.1">
<types>
<fieldType name="string" class="solr.StrField"/>
<fieldType name="long" class="${solr.tests.LongFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" positionIncrementGap="0"/>
</types>
<fields>
<field name="id" type="string" indexed="true" stored="true"/>
<field name="_version_" type="long" indexed="true" stored="true"/>
<dynamicField name="*" type="string" indexed="true" stored="true"/>
</fields>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -1,62 +0,0 @@
<?xml version="1.0" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF 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.
-->
<!-- This is a "kitchen sink" config file that tests can use.
When writting a new test, feel free to add *new* items (plugins,
config options, etc...) as long as they don't break any existing
tests. if you need to test something esoteric please add a new
"solrconfig-your-esoteric-purpose.xml" config file.
Note in particular that this test is used by MinimalSchemaTest so
Anything added to this file needs to work correctly even if there
is now uniqueKey or defaultSearch Field.
-->
<config>
<dataDir>${solr.data.dir:}</dataDir>
<directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
<updateRequestProcessorChain name="cdcr-processor-chain">
<processor class="solr.CdcrUpdateProcessorFactory"/>
<processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>
<requestHandler name="/cdcr" class="solr.CdcrRequestHandler">
</requestHandler>
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
</updateLog>
</updateHandler>
<requestHandler name="/select" class="solr.SearchHandler" />
<requestHandler name="/update" class="solr.UpdateRequestHandler">
<lst name="defaults">
<str name="update.chain">cdcr-processor-chain</str>
</lst>
</requestHandler>
</config>

View File

@ -1,906 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cloud.cdcr;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.cloud.AbstractDistribZkTestBase;
import org.apache.solr.cloud.AbstractZkTestCase;
import org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.ZkCoreNodeProps;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.IOUtils;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.TimeSource;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CoreDescriptor;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.CdcrParams;
import org.apache.solr.util.TimeOut;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.CREATE_NODE_SET;
import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.NUM_SLICES;
import static org.apache.solr.common.cloud.ZkStateReader.CLUSTER_PROPS;
import static org.apache.solr.common.cloud.ZkStateReader.REPLICATION_FACTOR;
import static org.apache.solr.handler.admin.CoreAdminHandler.COMPLETED;
import static org.apache.solr.handler.admin.CoreAdminHandler.RESPONSE_STATUS;
/**
* <p>
* Abstract class for CDCR unit testing. This class emulates two clusters, a source and target, by using different
* collections in the same SolrCloud cluster. Therefore, the two clusters will share the same Zookeeper cluster. In
* real scenario, the two collections/clusters will likely have their own zookeeper cluster.
* </p>
* <p>
* This class will automatically create two collections, the source and the target. Each collection will have
* {@link #shardCount} shards, and {@link #replicationFactor} replicas per shard. One jetty instance will
* be created per core.
* </p>
* <p>
* The source and target collection can be reinitialised at will by calling {@link #clearSourceCollection()} and
* {@link #clearTargetCollection()}. After reinitialisation, a collection will have a new fresh index and update log.
* </p>
* <p>
* Servers can be restarted at will by calling
* {@link #restartServer(BaseCdcrDistributedZkTest.CloudJettyRunner)} or
* {@link #restartServers(java.util.List)}.
* </p>
* <p>
* The creation of the target collection can be disabled with the flag {@link #createTargetCollection};
* </p>
* <p>
* NB: We cannot use multiple cores per jetty instance, as jetty will load only one core when restarting. It seems
* that this is a limitation of the {@link org.apache.solr.client.solrj.embedded.JettySolrRunner}. This class
* tries to ensure that there always is one single core per jetty instance.
* </p>
*/
public class BaseCdcrDistributedZkTest extends AbstractDistribZkTestBase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected int shardCount = 2;
protected int replicationFactor = 2;
protected boolean createTargetCollection = true;
private static final String CDCR_PATH = "/cdcr";
protected static final String SOURCE_COLLECTION = "source_collection";
protected static final String TARGET_COLLECTION = "target_collection";
public static final String SHARD1 = "shard1";
public static final String SHARD2 = "shard2";
@Override
protected String getCloudSolrConfig() {
return "solrconfig-cdcr.xml";
}
@Override
public void distribSetUp() throws Exception {
super.distribSetUp();
if (shardCount > 0) {
System.setProperty("numShards", Integer.toString(shardCount));
} else {
System.clearProperty("numShards");
}
if (isSSLMode()) {
System.clearProperty("urlScheme");
ZkStateReader zkStateReader = new ZkStateReader(zkServer.getZkAddress(),
AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT);
try {
zkStateReader.getZkClient().create(ZkStateReader.CLUSTER_PROPS,
Utils.toJSON(Collections.singletonMap("urlScheme", "https")),
CreateMode.PERSISTENT, true);
} catch (KeeperException.NodeExistsException e) {
ZkNodeProps props = ZkNodeProps.load(zkStateReader.getZkClient().getData(ZkStateReader.CLUSTER_PROPS,
null, null, true));
props = props.plus("urlScheme", "https");
zkStateReader.getZkClient().setData(CLUSTER_PROPS, Utils.toJSON(props), true);
} finally {
zkStateReader.close();
}
}
}
@Override
protected void createServers(int numServers) throws Exception {
}
@BeforeClass
public static void beforeClass() {
System.setProperty("solrcloud.update.delay", "0");
}
@AfterClass
public static void afterClass() throws Exception {
System.clearProperty("solrcloud.update.delay");
}
@Before
@SuppressWarnings({"rawtypes"})
public void baseBefore() throws Exception {
this.createSourceCollection();
if (this.createTargetCollection) this.createTargetCollection();
RandVal.uniqueValues = new HashSet(); //reset random values
}
@After
public void baseAfter() throws Exception {
for (List<CloudJettyRunner> runners : cloudJettys.values()) {
for (CloudJettyRunner runner : runners) {
runner.client.close();
}
}
destroyServers();
}
protected CloudSolrClient createCloudClient(String defaultCollection) {
CloudSolrClient server = getCloudSolrClient(zkServer.getZkAddress(), random().nextBoolean());
if (defaultCollection != null) server.setDefaultCollection(defaultCollection);
return server;
}
protected SolrInputDocument getDoc(Object... fields) throws Exception {
SolrInputDocument doc = new SolrInputDocument();
addFields(doc, fields);
return doc;
}
protected void index(String collection, SolrInputDocument doc) throws IOException, SolrServerException {
CloudSolrClient client = createCloudClient(collection);
try {
client.add(doc);
client.commit(true, true);
} finally {
client.close();
}
}
protected void index(String collection, List<SolrInputDocument> docs) throws IOException, SolrServerException {
CloudSolrClient client = createCloudClient(collection);
try {
client.add(docs);
client.commit(true, true);
} finally {
client.close();
}
}
protected void deleteById(String collection, List<String> ids) throws IOException, SolrServerException {
CloudSolrClient client = createCloudClient(collection);
try {
client.deleteById(ids);
client.commit(true, true);
} finally {
client.close();
}
}
protected void deleteByQuery(String collection, String q) throws IOException, SolrServerException {
CloudSolrClient client = createCloudClient(collection);
try {
client.deleteByQuery(q);
client.commit(true, true);
} finally {
client.close();
}
}
/**
* Invokes a commit on the given collection.
*/
protected void commit(String collection) throws IOException, SolrServerException {
CloudSolrClient client = createCloudClient(collection);
try {
client.commit(true, true);
} finally {
client.close();
}
}
/**
* Assert the number of documents in a given collection
*/
protected void assertNumDocs(int expectedNumDocs, String collection)
throws SolrServerException, IOException, InterruptedException {
CloudSolrClient client = createCloudClient(collection);
try {
int cnt = 30; // timeout after 15 seconds
AssertionError lastAssertionError = null;
while (cnt > 0) {
try {
assertEquals(expectedNumDocs, client.query(new SolrQuery("*:*")).getResults().getNumFound());
return;
}
catch (AssertionError e) {
lastAssertionError = e;
cnt--;
Thread.sleep(500);
}
}
throw new AssertionError("Timeout while trying to assert number of documents @ " + collection, lastAssertionError);
} finally {
client.close();
}
}
/**
* Invokes a CDCR action on a given node.
*/
@SuppressWarnings({"rawtypes"})
protected NamedList invokeCdcrAction(CloudJettyRunner jetty, CdcrParams.CdcrAction action) throws Exception {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CommonParams.ACTION, action.toString());
SolrRequest request = new QueryRequest(params);
request.setPath(CDCR_PATH);
return jetty.client.request(request);
}
protected void waitForCdcrStateReplication(String collection) throws Exception {
log.info("Wait for CDCR state to replicate - collection: {}", collection);
int cnt = 30;
while (cnt > 0) {
@SuppressWarnings({"rawtypes"})
NamedList status = null;
boolean allEquals = true;
for (CloudJettyRunner jetty : cloudJettys.get(collection)) { // check all replicas
@SuppressWarnings({"rawtypes"})
NamedList rsp = invokeCdcrAction(jetty, CdcrParams.CdcrAction.STATUS);
if (status == null) {
status = (NamedList) rsp.get(CdcrParams.CdcrAction.STATUS.toLower());
continue;
}
allEquals &= status.equals(rsp.get(CdcrParams.CdcrAction.STATUS.toLower()));
}
if (allEquals) {
break;
}
else {
if (cnt == 0) {
throw new RuntimeException("Timeout waiting for CDCR state to replicate: collection="+collection);
}
cnt--;
Thread.sleep(500);
}
}
log.info("CDCR state is identical across nodes - collection: {}", collection);
}
/**
* Assert the state of CDCR on each nodes of the given collection.
*/
protected void assertState(String collection, CdcrParams.ProcessState processState, CdcrParams.BufferState bufferState)
throws Exception {
this.waitForCdcrStateReplication(collection); // ensure that cdcr state is replicated and stable
for (CloudJettyRunner jetty : cloudJettys.get(collection)) { // check all replicas
@SuppressWarnings({"rawtypes"})
NamedList rsp = invokeCdcrAction(jetty, CdcrParams.CdcrAction.STATUS);
@SuppressWarnings({"rawtypes"})
NamedList status = (NamedList) rsp.get(CdcrParams.CdcrAction.STATUS.toLower());
assertEquals(processState.toLower(), status.get(CdcrParams.ProcessState.getParam()));
assertEquals(bufferState.toLower(), status.get(CdcrParams.BufferState.getParam()));
}
}
/**
* A mapping between collection and node names. This is used when creating the collection in
* {@link #createCollection(String)}.
*/
private Map<String, List<String>> collectionToNodeNames = new HashMap<>();
/**
* Starts the servers, saves and associates the node names to the source collection,
* and finally creates the source collection.
*/
private void createSourceCollection() throws Exception {
List<String> nodeNames = this.startServers(shardCount * replicationFactor);
this.collectionToNodeNames.put(SOURCE_COLLECTION, nodeNames);
this.createCollection(SOURCE_COLLECTION);
this.waitForRecoveriesToFinish(SOURCE_COLLECTION, true);
this.updateMappingsFromZk(SOURCE_COLLECTION);
}
/**
* Clear the source collection. It will delete then create the collection through the collection API.
* The collection will have a new fresh index, i.e., including a new update log.
*/
protected void clearSourceCollection() throws Exception {
this.deleteCollection(SOURCE_COLLECTION);
this.waitForCollectionToDisappear(SOURCE_COLLECTION);
this.createCollection(SOURCE_COLLECTION);
this.waitForRecoveriesToFinish(SOURCE_COLLECTION, true);
this.updateMappingsFromZk(SOURCE_COLLECTION);
}
/**
* Starts the servers, saves and associates the node names to the target collection,
* and finally creates the target collection.
*/
private void createTargetCollection() throws Exception {
List<String> nodeNames = this.startServers(shardCount * replicationFactor);
this.collectionToNodeNames.put(TARGET_COLLECTION, nodeNames);
this.createCollection(TARGET_COLLECTION);
this.waitForRecoveriesToFinish(TARGET_COLLECTION, true);
this.updateMappingsFromZk(TARGET_COLLECTION);
}
/**
* Clear the source collection. It will delete then create the collection through the collection API.
* The collection will have a new fresh index, i.e., including a new update log.
*/
protected void clearTargetCollection() throws Exception {
this.deleteCollection(TARGET_COLLECTION);
this.waitForCollectionToDisappear(TARGET_COLLECTION);
this.createCollection(TARGET_COLLECTION);
this.waitForRecoveriesToFinish(TARGET_COLLECTION, true);
this.updateMappingsFromZk(TARGET_COLLECTION);
}
/**
* Create a new collection through the Collection API. It enforces the use of one max shard per node.
* It will define the nodes to spread the new collection across by using the mapping {@link #collectionToNodeNames},
* to ensure that a node will not host more than one core (which will create problem when trying to restart servers).
*/
private void createCollection(String name) throws Exception {
CloudSolrClient client = createCloudClient(null);
try {
// Create the target collection
Map<String, List<Integer>> collectionInfos = new HashMap<>();
StringBuilder sb = new StringBuilder();
for (String nodeName : collectionToNodeNames.get(name)) {
sb.append(nodeName);
sb.append(',');
}
sb.deleteCharAt(sb.length() - 1);
createCollection(collectionInfos, name, shardCount, replicationFactor, client, sb.toString());
} finally {
client.close();
}
}
private CollectionAdminResponse createCollection(Map<String, List<Integer>> collectionInfos,
String collectionName, int numShards, int replicationFactor,
SolrClient client, String createNodeSetStr)
throws SolrServerException, IOException {
return createCollection(collectionInfos, collectionName,
Utils.makeMap(
NUM_SLICES, numShards,
REPLICATION_FACTOR, replicationFactor,
CREATE_NODE_SET, createNodeSetStr),
client, "conf1");
}
private CollectionAdminResponse createCollection(Map<String, List<Integer>> collectionInfos, String collectionName,
Map<String, Object> collectionProps, SolrClient client,
String confSetName)
throws SolrServerException, IOException {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("action", CollectionParams.CollectionAction.CREATE.toString());
for (Map.Entry<String, Object> entry : collectionProps.entrySet()) {
if (entry.getValue() != null) params.set(entry.getKey(), String.valueOf(entry.getValue()));
}
Integer numShards = (Integer) collectionProps.get(OverseerCollectionMessageHandler.NUM_SLICES);
if (numShards == null) {
String shardNames = (String) collectionProps.get(OverseerCollectionMessageHandler.SHARDS_PROP);
numShards = StrUtils.splitSmart(shardNames, ',').size();
}
Integer replicationFactor = (Integer) collectionProps.get(REPLICATION_FACTOR);
if (replicationFactor == null) {
replicationFactor = (Integer) OverseerCollectionMessageHandler.COLLECTION_PROPS_AND_DEFAULTS.get(REPLICATION_FACTOR);
}
if (confSetName != null) {
params.set("collection.configName", confSetName);
}
List<Integer> list = new ArrayList<>();
list.add(numShards);
list.add(replicationFactor);
if (collectionInfos != null) {
collectionInfos.put(collectionName, list);
}
params.set("name", collectionName);
@SuppressWarnings({"rawtypes"})
SolrRequest request = new QueryRequest(params);
request.setPath("/admin/collections");
CollectionAdminResponse res = new CollectionAdminResponse();
res.setResponse(client.request(request));
return res;
}
/**
* Delete a collection through the Collection API.
*/
protected CollectionAdminResponse deleteCollection(String collectionName) throws Exception {
SolrClient client = createCloudClient(null);
CollectionAdminResponse res;
try {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("action", CollectionParams.CollectionAction.DELETE.toString());
params.set("name", collectionName);
QueryRequest request = new QueryRequest(params);
request.setPath("/admin/collections");
res = new CollectionAdminResponse();
res.setResponse(client.request(request));
} catch (Exception e) {
log.warn("Error while deleting the collection {}", collectionName, e);
return new CollectionAdminResponse();
} finally {
client.close();
}
return res;
}
private void waitForCollectionToDisappear(String collection) throws Exception {
CloudSolrClient client = this.createCloudClient(null);
try {
client.connect();
ZkStateReader zkStateReader = client.getZkStateReader();
AbstractDistribZkTestBase.waitForCollectionToDisappear(collection, zkStateReader, true, 15);
} finally {
client.close();
}
}
private void waitForRecoveriesToFinish(String collection, boolean verbose) throws Exception {
CloudSolrClient client = this.createCloudClient(null);
try {
client.connect();
ZkStateReader zkStateReader = client.getZkStateReader();
super.waitForRecoveriesToFinish(collection, zkStateReader, verbose);
} finally {
client.close();
}
}
/**
* Asserts that the collection has the correct number of shards and replicas
*/
protected void assertCollectionExpectations(String collectionName) throws Exception {
CloudSolrClient client = this.createCloudClient(null);
try {
client.connect();
ClusterState clusterState = client.getZkStateReader().getClusterState();
assertTrue("Could not find new collection " + collectionName, clusterState.hasCollection(collectionName));
Map<String, Slice> shards = clusterState.getCollection(collectionName).getSlicesMap();
// did we find expectedSlices shards/shards?
assertEquals("Found new collection " + collectionName + ", but mismatch on number of shards.", shardCount, shards.size());
int totalShards = 0;
for (String shardName : shards.keySet()) {
totalShards += shards.get(shardName).getReplicas().size();
}
int expectedTotalShards = shardCount * replicationFactor;
assertEquals("Found new collection " + collectionName + " with correct number of shards, but mismatch on number " +
"of shards.", expectedTotalShards, totalShards);
} finally {
client.close();
}
}
/**
* Restart a server.
*/
protected void restartServer(CloudJettyRunner server) throws Exception {
// it seems we need to set the collection property to have the jetty properly restarted
System.setProperty("collection", server.collection);
JettySolrRunner jetty = server.jetty;
jetty.stop();
jetty.start();
System.clearProperty("collection");
waitForRecoveriesToFinish(server.collection, true);
updateMappingsFromZk(server.collection); // must update the mapping as the core node name might have changed
}
/**
* Restarts a list of servers.
*/
protected void restartServers(List<CloudJettyRunner> servers) throws Exception {
for (CloudJettyRunner server : servers) {
this.restartServer(server);
}
}
private List<JettySolrRunner> jettys = new ArrayList<>();
/**
* Creates and starts a given number of servers.
*/
protected List<String> startServers(int nServer) throws Exception {
String temporaryCollection = "tmp_collection";
for (int i = 1; i <= nServer; i++) {
// give everyone there own solrhome
File jettyDir = createTempDir("jetty").toFile();
jettyDir.mkdirs();
setupJettySolrHome(jettyDir);
JettySolrRunner jetty = createJetty(jettyDir, null, "shard" + i);
jetty.start();
jettys.add(jetty);
}
try (SolrClient client = createCloudClient(temporaryCollection)) {
assertEquals(0, CollectionAdminRequest
.createCollection(temporaryCollection, "conf1", shardCount, 1)
.setCreateNodeSet("")
.process(client).getStatus());
for (int i = 0; i < jettys.size(); i++) {
assertTrue(CollectionAdminRequest
.addReplicaToShard(temporaryCollection, "shard"+((i % shardCount) + 1))
.setNode(jettys.get(i).getNodeName())
.process(client).isSuccess());
}
}
ZkStateReader zkStateReader = jettys.get(0).getCoreContainer().getZkController().getZkStateReader();
// now wait till we see the leader for each shard
for (int i = 1; i <= shardCount; i++) {
zkStateReader.getLeaderRetry(temporaryCollection, "shard" + i, 15000);
}
// store the node names
List<String> nodeNames = new ArrayList<>();
for (Slice shard : zkStateReader.getClusterState().getCollection(temporaryCollection).getSlices()) {
for (Replica replica : shard.getReplicas()) {
nodeNames.add(replica.getNodeName());
}
}
this.waitForRecoveriesToFinish(temporaryCollection,zkStateReader, true);
// delete the temporary collection - we will create our own collections later
this.deleteCollection(temporaryCollection);
this.waitForCollectionToDisappear(temporaryCollection);
System.clearProperty("collection");
return nodeNames;
}
@Override
protected void destroyServers() throws Exception {
for (JettySolrRunner runner : jettys) {
try {
runner.stop();
} catch (Exception e) {
log.error("", e);
}
}
jettys.clear();
}
/**
* Mapping from collection to jettys
*/
protected Map<String, List<CloudJettyRunner>> cloudJettys = new HashMap<>();
/**
* Mapping from collection/shard to jettys
*/
protected Map<String, Map<String, List<CloudJettyRunner>>> shardToJetty = new HashMap<>();
/**
* Mapping from collection/shard leader to jettys
*/
protected Map<String, Map<String, CloudJettyRunner>> shardToLeaderJetty = new HashMap<>();
/**
* Updates the mappings between the jetty's instances and the zookeeper cluster state.
*/
protected void updateMappingsFromZk(String collection) throws Exception {
List<CloudJettyRunner> cloudJettys = new ArrayList<>();
Map<String, List<CloudJettyRunner>> shardToJetty = new HashMap<>();
Map<String, CloudJettyRunner> shardToLeaderJetty = new HashMap<>();
CloudSolrClient cloudClient = this.createCloudClient(null);
try {
cloudClient.connect();
ZkStateReader zkStateReader = cloudClient.getZkStateReader();
ClusterState clusterState = zkStateReader.getClusterState();
DocCollection coll = clusterState.getCollection(collection);
for (JettySolrRunner jetty : jettys) {
int port = jetty.getLocalPort();
if (port == -1) {
throw new RuntimeException("Cannot find the port for jetty");
}
nextJetty:
for (Slice shard : coll.getSlices()) {
Set<Map.Entry<String, Replica>> entries = shard.getReplicasMap().entrySet();
for (Map.Entry<String, Replica> entry : entries) {
Replica replica = entry.getValue();
if (replica.getStr(ZkStateReader.BASE_URL_PROP).contains(":" + port)) {
if (!shardToJetty.containsKey(shard.getName())) {
shardToJetty.put(shard.getName(), new ArrayList<CloudJettyRunner>());
}
boolean isLeader = shard.getLeader() == replica;
CloudJettyRunner cjr = new CloudJettyRunner(jetty, replica, collection, shard.getName(), entry.getKey());
shardToJetty.get(shard.getName()).add(cjr);
if (isLeader) {
shardToLeaderJetty.put(shard.getName(), cjr);
}
cloudJettys.add(cjr);
break nextJetty;
}
}
}
}
List<CloudJettyRunner> oldRunners = this.cloudJettys.putIfAbsent(collection, cloudJettys);
if (oldRunners != null) {
// must close resources for the old entries
for (CloudJettyRunner oldRunner : oldRunners) {
IOUtils.closeQuietly(oldRunner.client);
}
}
this.cloudJettys.put(collection, cloudJettys);
this.shardToJetty.put(collection, shardToJetty);
this.shardToLeaderJetty.put(collection, shardToLeaderJetty);
} finally {
cloudClient.close();
}
}
/**
* Wrapper around a {@link org.apache.solr.client.solrj.embedded.JettySolrRunner} to map the jetty
* instance to various information of the cloud cluster, such as the collection and shard
* that is served by the jetty instance, the node name, core node name, url, etc.
*/
public static class CloudJettyRunner {
public JettySolrRunner jetty;
public String nodeName;
public String coreNodeName;
public String url;
public SolrClient client;
public Replica info;
public String shard;
public String collection;
public CloudJettyRunner(JettySolrRunner jetty, Replica replica,
String collection, String shard, String coreNodeName) {
this.jetty = jetty;
this.info = replica;
this.collection = collection;
Properties nodeProperties = jetty.getNodeProperties();
// we need to update the jetty's shard so that it registers itself to the right shard when restarted
this.shard = shard;
nodeProperties.setProperty(CoreDescriptor.CORE_SHARD, this.shard);
// we need to update the jetty's shard so that it registers itself under the right core name when restarted
this.coreNodeName = coreNodeName;
nodeProperties.setProperty(CoreDescriptor.CORE_NODE_NAME, this.coreNodeName);
this.nodeName = replica.getNodeName();
ZkCoreNodeProps coreNodeProps = new ZkCoreNodeProps(info);
this.url = coreNodeProps.getCoreUrl();
// strip the trailing slash as this can cause issues when executing requests
this.client = createNewSolrServer(this.url.substring(0, this.url.length() - 1));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((url == null) ? 0 : url.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
CloudJettyRunner other = (CloudJettyRunner) obj;
if (url == null) {
if (other.url != null) return false;
} else if (!url.equals(other.url)) return false;
return true;
}
@Override
public String toString() {
return "CloudJettyRunner [url=" + url + "]";
}
}
protected static SolrClient createNewSolrServer(String baseUrl) {
try {
// setup the server...
HttpSolrClient s = getHttpSolrClient(baseUrl, DEFAULT_CONNECTION_TIMEOUT);
return s;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
protected void waitForBootstrapToComplete(String collectionName, String shardId) throws Exception {
@SuppressWarnings({"rawtypes"})
NamedList rsp;// we need to wait until bootstrap is complete otherwise the replicator thread will never start
TimeOut timeOut = new TimeOut(60, TimeUnit.SECONDS, TimeSource.NANO_TIME);
while (!timeOut.hasTimedOut()) {
rsp = invokeCdcrAction(shardToLeaderJetty.get(collectionName).get(shardId), CdcrParams.CdcrAction.BOOTSTRAP_STATUS);
if (rsp.get(RESPONSE_STATUS).toString().equals(COMPLETED)) {
break;
}
Thread.sleep(1000);
}
}
protected void waitForReplicationToComplete(String collectionName, String shardId) throws Exception {
int cnt = 15;
while (cnt > 0) {
log.info("Checking queue size @ {}:{}", collectionName, shardId);
long size = this.getQueueSize(collectionName, shardId);
if (size == 0) { // if we received -1, it means that the log reader is not yet initialised, we should wait
return;
}
log.info("Waiting for replication to complete. Queue size: {} @ {}:{}", size, collectionName, shardId);
cnt--;
Thread.sleep(1000); // wait a bit for the replication to complete
}
throw new RuntimeException("Timeout waiting for CDCR replication to complete @" + collectionName + ":" + shardId);
}
protected long getQueueSize(String collectionName, String shardId) throws Exception {
@SuppressWarnings({"rawtypes"})
NamedList rsp = this.invokeCdcrAction(shardToLeaderJetty.get(collectionName).get(shardId), CdcrParams.CdcrAction.QUEUES);
@SuppressWarnings({"rawtypes"})
NamedList host = (NamedList) ((NamedList) rsp.get(CdcrParams.QUEUES)).getVal(0);
@SuppressWarnings({"rawtypes"})
NamedList status = (NamedList) host.get(TARGET_COLLECTION);
return (Long) status.get(CdcrParams.QUEUE_SIZE);
}
protected CollectionInfo collectInfo(String collection) throws Exception {
CollectionInfo info = new CollectionInfo(collection);
for (String shard : shardToJetty.get(collection).keySet()) {
List<CloudJettyRunner> jettyRunners = shardToJetty.get(collection).get(shard);
for (CloudJettyRunner jettyRunner : jettyRunners) {
for (SolrCore core : jettyRunner.jetty.getCoreContainer().getCores()) {
info.addCore(core, shard, shardToLeaderJetty.get(collection).containsValue(jettyRunner));
}
}
}
return info;
}
protected static class CollectionInfo {
List<CoreInfo> coreInfos = new ArrayList<>();
String collection;
CollectionInfo(String collection) {
this.collection = collection;
}
/**
* @return Returns a map shard -> list of cores
*/
Map<String, List<CoreInfo>> getShardToCoresMap() {
Map<String, List<CoreInfo>> map = new HashMap<>();
for (CoreInfo info : coreInfos) {
List<CoreInfo> list = map.get(info.shard);
if (list == null) {
list = new ArrayList<>();
map.put(info.shard, list);
}
list.add(info);
}
return map;
}
CoreInfo getLeader(String shard) {
List<CoreInfo> coreInfos = getShardToCoresMap().get(shard);
for (CoreInfo info : coreInfos) {
if (info.isLeader) {
return info;
}
}
assertTrue(String.format(Locale.ENGLISH, "There is no leader for collection %s shard %s", collection, shard), false);
return null;
}
List<CoreInfo> getReplicas(String shard) {
List<CoreInfo> coreInfos = getShardToCoresMap().get(shard);
coreInfos.remove(getLeader(shard));
return coreInfos;
}
void addCore(SolrCore core, String shard, boolean isLeader) throws Exception {
CoreInfo info = new CoreInfo();
info.collectionName = core.getName();
info.shard = shard;
info.isLeader = isLeader;
info.ulogDir = core.getUpdateHandler().getUpdateLog().getLogDir();
this.coreInfos.add(info);
}
public static class CoreInfo {
String collectionName;
String shard;
boolean isLeader;
String ulogDir;
}
}
}

View File

@ -1,244 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cloud.cdcr;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.TimeUnit;
import com.google.common.collect.ImmutableMap;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.MiniSolrCloudCluster;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.TimeSource;
import org.apache.solr.handler.CdcrParams;
import org.apache.solr.util.TimeOut;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CdcrBidirectionalTest extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Test
@AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/SOLR-12524")
public void testBiDir() throws Exception {
MiniSolrCloudCluster cluster2 = new MiniSolrCloudCluster(1, createTempDir("cdcr-cluster2"), buildJettyConfig("/solr"));
MiniSolrCloudCluster cluster1 = new MiniSolrCloudCluster(1, createTempDir("cdcr-cluster1"), buildJettyConfig("/solr"));
try {
if (log.isInfoEnabled()) {
log.info("cluster2 zkHost = {}", cluster2.getZkServer().getZkAddress());
}
System.setProperty("cdcr.cluster2.zkHost", cluster2.getZkServer().getZkAddress());
if (log.isInfoEnabled()) {
log.info("cluster1 zkHost = {}", cluster1.getZkServer().getZkAddress());
}
System.setProperty("cdcr.cluster1.zkHost", cluster1.getZkServer().getZkAddress());
cluster1.uploadConfigSet(configset("cdcr-cluster1"), "cdcr-cluster1");
CollectionAdminRequest.createCollection("cdcr-cluster1", "cdcr-cluster1", 2, 1)
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(cluster1.getSolrClient());
CloudSolrClient cluster1SolrClient = cluster1.getSolrClient();
cluster1SolrClient.setDefaultCollection("cdcr-cluster1");
cluster2.uploadConfigSet(configset("cdcr-cluster2"), "cdcr-cluster2");
CollectionAdminRequest.createCollection("cdcr-cluster2", "cdcr-cluster2", 2, 1)
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(cluster2.getSolrClient());
CloudSolrClient cluster2SolrClient = cluster2.getSolrClient();
cluster2SolrClient.setDefaultCollection("cdcr-cluster2");
UpdateRequest req = null;
CdcrTestsUtil.cdcrStart(cluster1SolrClient);
Thread.sleep(2000);
// ADD operation on cluster 1
int docs = (TEST_NIGHTLY ? 100 : 10);
int numDocs_c1 = 0;
for (int k = 0; k < docs; k++) {
req = new UpdateRequest();
for (; numDocs_c1 < (k + 1) * 100; numDocs_c1++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "cluster1_" + numDocs_c1);
doc.addField("xyz", numDocs_c1);
req.add(doc);
}
req.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
log.info("Adding {} docs with commit=true, numDocs={}", docs, numDocs_c1);
req.process(cluster1SolrClient);
}
QueryResponse response = cluster1SolrClient.query(new SolrQuery("*:*"));
assertEquals("cluster 1 docs mismatch", numDocs_c1, response.getResults().getNumFound());
assertEquals("cluster 2 docs mismatch", numDocs_c1, CdcrTestsUtil.waitForClusterToSync(numDocs_c1, cluster2SolrClient));
CdcrTestsUtil.cdcrStart(cluster2SolrClient); // FULL BI-DIRECTIONAL CDCR FORWARDING ON
Thread.sleep(2000);
// ADD operation on cluster 2
int numDocs_c2 = 0;
for (int k = 0; k < docs; k++) {
req = new UpdateRequest();
for (; numDocs_c2 < (k + 1) * 100; numDocs_c2++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "cluster2_" + numDocs_c2);
doc.addField("xyz", numDocs_c2);
req.add(doc);
}
req.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
log.info("Adding {} docs with commit=true, numDocs= {}", docs, numDocs_c2);
req.process(cluster2SolrClient);
}
int numDocs = numDocs_c1 + numDocs_c2;
response = cluster2SolrClient.query(new SolrQuery("*:*"));
assertEquals("cluster 2 docs mismatch", numDocs, response.getResults().getNumFound());
assertEquals("cluster 1 docs mismatch", numDocs, CdcrTestsUtil.waitForClusterToSync(numDocs, cluster1SolrClient));
// logging cdcr clusters queue response
response = CdcrTestsUtil.getCdcrQueue(cluster1SolrClient);
if (log.isInfoEnabled()) {
log.info("Cdcr cluster1 queue response: {}", response.getResponse());
}
response = CdcrTestsUtil.getCdcrQueue(cluster2SolrClient);
if (log.isInfoEnabled()) {
log.info("Cdcr cluster2 queue response: {}", response.getResponse());
}
// lets find and keep the maximum version assigned by cluster1 & cluster2 across all our updates
long maxVersion_c1 = Math.min((long)CdcrTestsUtil.getFingerPrintMaxVersion(cluster1SolrClient, "shard1", numDocs),
(long)CdcrTestsUtil.getFingerPrintMaxVersion(cluster1SolrClient, "shard2", numDocs));
long maxVersion_c2 = Math.min((long)CdcrTestsUtil.getFingerPrintMaxVersion(cluster2SolrClient, "shard1", numDocs),
(long)CdcrTestsUtil.getFingerPrintMaxVersion(cluster2SolrClient, "shard2", numDocs));
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CommonParams.ACTION, CdcrParams.CdcrAction.COLLECTIONCHECKPOINT.toString());
params.set(CommonParams.QT, "/cdcr");
response = cluster2SolrClient.query(params);
Long checkpoint_2 = (Long) response.getResponse().get(CdcrParams.CHECKPOINT);
assertNotNull(checkpoint_2);
params = new ModifiableSolrParams();
params.set(CommonParams.ACTION, CdcrParams.CdcrAction.COLLECTIONCHECKPOINT.toString());
params.set(CommonParams.QT, "/cdcr");
response = cluster1SolrClient.query(params);
Long checkpoint_1 = (Long) response.getResponse().get(CdcrParams.CHECKPOINT);
assertNotNull(checkpoint_1);
log.info("v1: {}\tv2: {}\tcheckpoint1: {}\tcheckpoint2: {}"
, maxVersion_c1, maxVersion_c2, checkpoint_1, checkpoint_2);
assertEquals("COLLECTIONCHECKPOINT from cluster2 should have returned the maximum " +
"version across all updates made to cluster1", maxVersion_c1, checkpoint_2.longValue());
assertEquals("COLLECTIONCHECKPOINT from cluster1 should have returned the maximum " +
"version across all updates made to cluster2", maxVersion_c2, checkpoint_1.longValue());
assertEquals("max versions of updates in both clusters should be same", maxVersion_c1, maxVersion_c2);
// DELETE BY QUERY
String deleteByQuery = "id:cluster1_" +String.valueOf(random().nextInt(numDocs_c1));
response = cluster1SolrClient.query(new SolrQuery(deleteByQuery));
assertEquals("should match exactly one doc", 1, response.getResults().getNumFound());
cluster1SolrClient.deleteByQuery(deleteByQuery);
cluster1SolrClient.commit();
numDocs--;
numDocs_c1--;
response = cluster1SolrClient.query(new SolrQuery("*:*"));
assertEquals("cluster 1 docs mismatch", numDocs, response.getResults().getNumFound());
assertEquals("cluster 2 docs mismatch", numDocs, CdcrTestsUtil.waitForClusterToSync(numDocs, cluster2SolrClient));
// DELETE BY ID
SolrInputDocument doc;
String delete_id_query = "cluster2_" + random().nextInt(numDocs_c2);
cluster2SolrClient.deleteById(delete_id_query);
cluster2SolrClient.commit();
numDocs--;
numDocs_c2--;
response = cluster2SolrClient.query(new SolrQuery("*:*"));
assertEquals("cluster 2 docs mismatch", numDocs, response.getResults().getNumFound());
assertEquals("cluster 1 docs mismatch", numDocs, CdcrTestsUtil.waitForClusterToSync(numDocs, cluster1SolrClient));
// ATOMIC UPDATES
req = new UpdateRequest();
doc = new SolrInputDocument();
String atomicFieldName = "abc";
String atomicUpdateId = "cluster2_" + random().nextInt(numDocs_c2);
doc.addField("id", atomicUpdateId);
doc.addField("xyz", ImmutableMap.of("delete", ""));
doc.addField(atomicFieldName, ImmutableMap.of("set", "ABC"));
req.add(doc);
req.process(cluster2SolrClient);
cluster2SolrClient.commit();
String atomicQuery = "id:" + atomicUpdateId;
response = cluster2SolrClient.query(new SolrQuery(atomicQuery));
assertEquals("cluster 2 wrong doc", "ABC", response.getResults().get(0).get(atomicFieldName));
assertEquals("cluster 1 wrong doc", "ABC", getDocFieldValue(cluster1SolrClient, atomicQuery, "ABC", atomicFieldName ));
// logging cdcr clusters queue response
response = CdcrTestsUtil.getCdcrQueue(cluster1SolrClient);
if (log.isInfoEnabled()) {
log.info("Cdcr cluster1 queue response at end of testcase: {}", response.getResponse());
}
response = CdcrTestsUtil.getCdcrQueue(cluster2SolrClient);
if (log.isInfoEnabled()) {
log.info("Cdcr cluster2 queue response at end of testcase: {}", response.getResponse());
}
CdcrTestsUtil.cdcrStop(cluster1SolrClient);
CdcrTestsUtil.cdcrStop(cluster2SolrClient);
} finally {
if (cluster1 != null) {
cluster1.shutdown();
}
if (cluster2 != null) {
cluster2.shutdown();
}
}
}
private String getDocFieldValue(CloudSolrClient clusterSolrClient, String query, String match, String field) throws Exception {
TimeOut waitTimeOut = new TimeOut(30, TimeUnit.SECONDS, TimeSource.NANO_TIME);
while (!waitTimeOut.hasTimedOut()) {
clusterSolrClient.commit();
QueryResponse response = clusterSolrClient.query(new SolrQuery(query));
if (response.getResults().size() > 0 && match.equals(response.getResults().get(0).get(field))) {
return (String) response.getResults().get(0).get(field);
}
Thread.sleep(1000);
}
return null;
}
}

View File

@ -1,373 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cloud.cdcr;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.LinkedHashMap;
import org.apache.lucene.store.FSDirectory;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.AbstractDistribZkTestBase;
import org.apache.solr.cloud.MiniSolrCloudCluster;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.handler.CdcrParams;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CdcrBootstrapTest extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/**
* Starts a source cluster with no CDCR configuration, indexes enough documents such that
* the at least one old tlog is closed and thrown away so that the source cluster does not have
* all updates available in tlogs only.
* <p>
* Then we start a target cluster with CDCR configuration and we change the source cluster configuration
* to use CDCR (i.e. CdcrUpdateLog, CdcrRequestHandler and CdcrUpdateProcessor) and restart it.
* <p>
* We test that all updates eventually make it to the target cluster and that the collectioncheckpoint
* call returns the same version as the last update indexed on the source.
*/
@Test
// commented 4-Sep-2018 @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2-Aug-2018
// commented out on: 17-Feb-2019 @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 14-Oct-2018
public void testConvertClusterToCdcrAndBootstrap() throws Exception {
// start the target first so that we know its zkhost
MiniSolrCloudCluster target = new MiniSolrCloudCluster(1, createTempDir("cdcr-target"), buildJettyConfig("/solr"));
try {
if (log.isInfoEnabled()) {
log.info("Target zkHost = {}", target.getZkServer().getZkAddress());
}
System.setProperty("cdcr.target.zkHost", target.getZkServer().getZkAddress());
// start a cluster with no cdcr
MiniSolrCloudCluster source = new MiniSolrCloudCluster(1, createTempDir("cdcr-source"), buildJettyConfig("/solr"));
try {
source.uploadConfigSet(configset("cdcr-source-disabled"), "cdcr-source");
// create a collection with the cdcr-source-disabled configset
CollectionAdminRequest.createCollection("cdcr-source", "cdcr-source", 1, 1)
// todo investigate why this is necessary??? because by default it selects a ram directory which deletes the tlogs on reloads?
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(source.getSolrClient());
source.waitForActiveCollection("cdcr-source", 1, 1);
CloudSolrClient sourceSolrClient = source.getSolrClient();
int docs = (TEST_NIGHTLY ? 100 : 10);
int numDocs = indexDocs(sourceSolrClient, "cdcr-source", docs);
QueryResponse response = sourceSolrClient.query(new SolrQuery("*:*"));
assertEquals("", numDocs, response.getResults().getNumFound());
// lets find and keep the maximum version assigned by source cluster across all our updates
long maxVersion = Long.MIN_VALUE;
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CommonParams.QT, "/get");
params.set("getVersions", numDocs);
params.set("fingerprint", true);
response = sourceSolrClient.query(params);
maxVersion = (long)(((LinkedHashMap)response.getResponse().get("fingerprint")).get("maxVersionEncountered"));
// upload the cdcr-enabled config and restart source cluster
source.uploadConfigSet(configset("cdcr-source"), "cdcr-source");
JettySolrRunner runner = source.stopJettySolrRunner(0);
source.waitForJettyToStop(runner);
source.startJettySolrRunner(runner);
source.waitForAllNodes(30);
assertTrue(runner.isRunning());
AbstractDistribZkTestBase.waitForRecoveriesToFinish("cdcr-source", source.getSolrClient().getZkStateReader(), true, true, 330);
response = sourceSolrClient.query(new SolrQuery("*:*"));
assertEquals("Document mismatch on source after restart", numDocs, response.getResults().getNumFound());
// setup the target cluster
target.uploadConfigSet(configset("cdcr-target"), "cdcr-target");
CollectionAdminRequest.createCollection("cdcr-target", "cdcr-target", 1, 2)
.process(target.getSolrClient());
target.waitForActiveCollection("cdcr-target", 1, 2);
CloudSolrClient targetSolrClient = target.getSolrClient();
targetSolrClient.setDefaultCollection("cdcr-target");
Thread.sleep(6000);
CdcrTestsUtil.cdcrStart(targetSolrClient);
CdcrTestsUtil.cdcrStart(sourceSolrClient);
response = CdcrTestsUtil.getCdcrQueue(sourceSolrClient);
if (log.isInfoEnabled()) {
log.info("Cdcr queue response: {}", response.getResponse());
}
long foundDocs = CdcrTestsUtil.waitForClusterToSync(numDocs, targetSolrClient);
assertEquals("Document mismatch on target after sync", numDocs, foundDocs);
assertTrue(CdcrTestsUtil.assertShardInSync("cdcr-target", "shard1", targetSolrClient)); // with more than 1 replica
params = new ModifiableSolrParams();
params.set(CommonParams.ACTION, CdcrParams.CdcrAction.COLLECTIONCHECKPOINT.toString());
params.set(CommonParams.QT, "/cdcr");
response = targetSolrClient.query(params);
Long checkpoint = (Long) response.getResponse().get(CdcrParams.CHECKPOINT);
assertNotNull(checkpoint);
assertEquals("COLLECTIONCHECKPOINT from target cluster should have returned the maximum " +
"version across all updates made to source", maxVersion, checkpoint.longValue());
} finally {
source.shutdown();
}
} finally {
target.shutdown();
}
}
private int indexDocs(CloudSolrClient sourceSolrClient, String collection, int batches) throws IOException, SolrServerException {
sourceSolrClient.setDefaultCollection(collection);
int numDocs = 0;
for (int k = 0; k < batches; k++) {
UpdateRequest req = new UpdateRequest();
for (; numDocs < (k + 1) * 100; numDocs++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "source_" + numDocs);
doc.addField("xyz", numDocs);
req.add(doc);
}
req.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
req.process(sourceSolrClient);
}
log.info("Adding numDocs={}", numDocs);
return numDocs;
}
/**
* This test start cdcr source, adds data,starts target cluster, verifies replication,
* stops cdcr replication and buffering, adds more data, re-enables cdcr and verify replication
*/
public void testBootstrapWithSourceCluster() throws Exception {
// start the target first so that we know its zkhost
MiniSolrCloudCluster target = new MiniSolrCloudCluster(1, createTempDir("cdcr-target"), buildJettyConfig("/solr"));
try {
System.out.println("Target zkHost = " + target.getZkServer().getZkAddress());
System.setProperty("cdcr.target.zkHost", target.getZkServer().getZkAddress());
MiniSolrCloudCluster source = new MiniSolrCloudCluster(1, createTempDir("cdcr-source"), buildJettyConfig("/solr"));
try {
source.uploadConfigSet(configset("cdcr-source"), "cdcr-source");
CollectionAdminRequest.createCollection("cdcr-source", "cdcr-source", 1, 1)
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(source.getSolrClient());
source.waitForActiveCollection("cdcr-source", 1, 1);
CloudSolrClient sourceSolrClient = source.getSolrClient();
int docs = (TEST_NIGHTLY ? 100 : 10);
int numDocs = indexDocs(sourceSolrClient, "cdcr-source", docs);
QueryResponse response = sourceSolrClient.query(new SolrQuery("*:*"));
assertEquals("", numDocs, response.getResults().getNumFound());
// setup the target cluster
target.uploadConfigSet(configset("cdcr-target"), "cdcr-target");
CollectionAdminRequest.createCollection("cdcr-target", "cdcr-target", 1, 1)
.process(target.getSolrClient());
target.waitForActiveCollection("cdcr-target", 1, 1);
CloudSolrClient targetSolrClient = target.getSolrClient();
targetSolrClient.setDefaultCollection("cdcr-target");
CdcrTestsUtil.cdcrStart(targetSolrClient);
CdcrTestsUtil.cdcrStart(sourceSolrClient);
response = CdcrTestsUtil.getCdcrQueue(sourceSolrClient);
if (log.isInfoEnabled()) {
log.info("Cdcr queue response: {}", response.getResponse());
}
long foundDocs = CdcrTestsUtil.waitForClusterToSync(numDocs, targetSolrClient);
assertEquals("Document mismatch on target after sync", numDocs, foundDocs);
int total_tlogs_in_index = FSDirectory.open(target.getBaseDir().resolve("node1").
resolve("cdcr-target_shard1_replica_n1").resolve("data").
resolve("tlog")).listAll().length;
assertEquals("tlogs count should be ZERO",0, total_tlogs_in_index);
CdcrTestsUtil.cdcrStop(sourceSolrClient);
CdcrTestsUtil.cdcrDisableBuffer(sourceSolrClient);
int c = 0;
for (int k = 0; k < 10; k++) {
UpdateRequest req = new UpdateRequest();
for (; c < (k + 1) * 100; c++, numDocs++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "source_" + numDocs);
doc.addField("xyz", numDocs);
req.add(doc);
}
req.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
log.info("Adding 100 docs with commit=true, numDocs={}", numDocs);
req.process(sourceSolrClient);
}
response = sourceSolrClient.query(new SolrQuery("*:*"));
assertEquals("", numDocs, response.getResults().getNumFound());
CdcrTestsUtil.cdcrStart(sourceSolrClient);
CdcrTestsUtil.cdcrEnableBuffer(sourceSolrClient);
foundDocs = CdcrTestsUtil.waitForClusterToSync(numDocs, targetSolrClient);
assertEquals("Document mismatch on target after sync", numDocs, foundDocs);
} finally {
source.shutdown();
}
} finally {
target.shutdown();
}
}
/**
* This test successfully validates the follower nodes at target copies content
* from their respective leaders
*/
public void testBootstrapWithMultipleReplicas() throws Exception {
// start the target first so that we know its zkhost
MiniSolrCloudCluster target = new MiniSolrCloudCluster(3, createTempDir("cdcr-target"), buildJettyConfig("/solr"));
try {
System.out.println("Target zkHost = " + target.getZkServer().getZkAddress());
System.setProperty("cdcr.target.zkHost", target.getZkServer().getZkAddress());
MiniSolrCloudCluster source = new MiniSolrCloudCluster(3, createTempDir("cdcr-source"), buildJettyConfig("/solr"));
try {
source.uploadConfigSet(configset("cdcr-source"), "cdcr-source");
CollectionAdminRequest.createCollection("cdcr-source", "cdcr-source", 1, 3)
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(source.getSolrClient());
source.waitForActiveCollection("cdcr-source", 1, 3);
CloudSolrClient sourceSolrClient = source.getSolrClient();
int docs = (TEST_NIGHTLY ? 100 : 10);
int numDocs = indexDocs(sourceSolrClient, "cdcr-source", docs);
QueryResponse response = sourceSolrClient.query(new SolrQuery("*:*"));
assertEquals("", numDocs, response.getResults().getNumFound());
// setup the target cluster
target.uploadConfigSet(configset("cdcr-target"), "cdcr-target");
CollectionAdminRequest.createCollection("cdcr-target", "cdcr-target", 1, 3)
.process(target.getSolrClient());
target.waitForActiveCollection("cdcr-target", 1, 3);
CloudSolrClient targetSolrClient = target.getSolrClient();
targetSolrClient.setDefaultCollection("cdcr-target");
CdcrTestsUtil.cdcrStart(targetSolrClient);
CdcrTestsUtil.cdcrStart(sourceSolrClient);
response = CdcrTestsUtil.getCdcrQueue(sourceSolrClient);
if (log.isInfoEnabled()) {
log.info("Cdcr queue response: {}", response.getResponse());
}
long foundDocs = CdcrTestsUtil.waitForClusterToSync(numDocs, targetSolrClient);
assertEquals("Document mismatch on target after sync", numDocs, foundDocs);
assertTrue("leader followers didnt' match", CdcrTestsUtil.assertShardInSync("cdcr-target", "shard1", targetSolrClient)); // with more than 1 replica
} finally {
source.shutdown();
}
} finally {
target.shutdown();
}
}
// 29-June-2018 @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
@BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 6-Sep-2018
@Test
@AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/SOLR-12028")
public void testBootstrapWithContinousIndexingOnSourceCluster() throws Exception {
// start the target first so that we know its zkhost
MiniSolrCloudCluster target = new MiniSolrCloudCluster(1, createTempDir("cdcr-target"), buildJettyConfig("/solr"));
try {
if (log.isInfoEnabled()) {
log.info("Target zkHost = {}", target.getZkServer().getZkAddress());
}
System.setProperty("cdcr.target.zkHost", target.getZkServer().getZkAddress());
MiniSolrCloudCluster source = new MiniSolrCloudCluster(1, createTempDir("cdcr-source"), buildJettyConfig("/solr"));
try {
source.uploadConfigSet(configset("cdcr-source"), "cdcr-source");
CollectionAdminRequest.createCollection("cdcr-source", "cdcr-source", 1, 1)
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(source.getSolrClient());
source.waitForActiveCollection("cdcr-source", 1, 1);
CloudSolrClient sourceSolrClient = source.getSolrClient();
int docs = (TEST_NIGHTLY ? 100 : 10);
int numDocs = indexDocs(sourceSolrClient, "cdcr-source", docs);
QueryResponse response = sourceSolrClient.query(new SolrQuery("*:*"));
assertEquals("", numDocs, response.getResults().getNumFound());
// setup the target cluster
target.uploadConfigSet(configset("cdcr-target"), "cdcr-target");
CollectionAdminRequest.createCollection("cdcr-target", "cdcr-target", 1, 1)
.process(target.getSolrClient());
target.waitForActiveCollection("cdcr-target", 1, 1);
CloudSolrClient targetSolrClient = target.getSolrClient();
targetSolrClient.setDefaultCollection("cdcr-target");
Thread.sleep(1000);
CdcrTestsUtil.cdcrStart(targetSolrClient);
CdcrTestsUtil.cdcrStart(sourceSolrClient);
int c = 0;
for (int k = 0; k < docs; k++) {
UpdateRequest req = new UpdateRequest();
for (; c < (k + 1) * 100; c++, numDocs++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "source_" + numDocs);
doc.addField("xyz", numDocs);
req.add(doc);
}
req.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
log.info("Adding {} docs with commit=true, numDocs={}", docs, numDocs);
req.process(sourceSolrClient);
}
response = sourceSolrClient.query(new SolrQuery("*:*"));
assertEquals("", numDocs, response.getResults().getNumFound());
response = CdcrTestsUtil.getCdcrQueue(sourceSolrClient);
if (log.isInfoEnabled()) {
log.info("Cdcr queue response: {}", response.getResponse());
}
long foundDocs = CdcrTestsUtil.waitForClusterToSync(numDocs, targetSolrClient);
assertEquals("Document mismatch on target after sync", numDocs, foundDocs);
} finally {
source.shutdown();
}
} finally {
target.shutdown();
}
}
}

View File

@ -1,332 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cloud.cdcr;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.MiniSolrCloudCluster;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.handler.CdcrParams;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.carrotsearch.randomizedtesting.annotations.Nightly;
@Nightly // test is too long for non nightly
public class CdcrOpsAndBoundariesTest extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
MiniSolrCloudCluster target, source;
CloudSolrClient sourceSolrClient, targetSolrClient;
private static String SOURCE_COLLECTION = "cdcr-source";
private static String TARGET_COLLECTION = "cdcr-target";
private static String ALL_Q = "*:*";
@Before
public void before() throws Exception {
target = new MiniSolrCloudCluster(1, createTempDir(TARGET_COLLECTION), buildJettyConfig("/solr"));
System.setProperty("cdcr.target.zkHost", target.getZkServer().getZkAddress());
source = new MiniSolrCloudCluster(1, createTempDir(SOURCE_COLLECTION), buildJettyConfig("/solr"));
}
@After
public void after() throws Exception {
if (null != target) {
target.shutdown();
target = null;
}
if (null != source) {
source.shutdown();
source = null;
}
}
/**
* Check the ops statistics.
*/
@Test
@SuppressWarnings({"rawtypes"})
public void testOps() throws Exception {
createCollections();
// Start CDCR
CdcrTestsUtil.cdcrRestart(sourceSolrClient);
// Index documents
CdcrTestsUtil.indexRandomDocs(100, sourceSolrClient);
double opsAll = 0.0;
NamedList ops = null;
// calculate ops
int itr = 10;
while (itr-- > 0 && opsAll == 0.0) {
NamedList rsp = CdcrTestsUtil.invokeCdcrAction(sourceSolrClient, CdcrParams.CdcrAction.OPS).getResponse();
NamedList collections = (NamedList) ((NamedList) rsp.get(CdcrParams.OPERATIONS_PER_SECOND)).getVal(0);
ops = (NamedList) collections.get(TARGET_COLLECTION);
opsAll = (Double) ops.get(CdcrParams.COUNTER_ALL);
Thread.sleep(250); // wait for cdcr to complete and check
}
// asserts ops values
double opsAdds = (Double) ops.get(CdcrParams.COUNTER_ADDS);
assertTrue(opsAll > 0);
assertTrue(opsAdds > 0);
double opsDeletes = (Double) ops.get(CdcrParams.COUNTER_DELETES);
assertEquals(0, opsDeletes, 0);
// Delete 10 documents: 10-19
List<String> ids;
for (int id = 0; id < 50; id++) {
ids = new ArrayList<>();
ids.add(Integer.toString(id));
sourceSolrClient.deleteById(ids, 1);
int dbq_id = 50 + id;
sourceSolrClient.deleteByQuery("id:" + dbq_id, 1);
}
itr = 10;
while (itr-- > 0) {
NamedList rsp = CdcrTestsUtil.invokeCdcrAction(sourceSolrClient, CdcrParams.CdcrAction.OPS).getResponse();
NamedList collections = (NamedList) ((NamedList) rsp.get(CdcrParams.OPERATIONS_PER_SECOND)).getVal(0);
ops = (NamedList) collections.get(TARGET_COLLECTION);
opsAll = (Double) ops.get(CdcrParams.COUNTER_ALL);
Thread.sleep(250); // wait for cdcr to complete and check
}
// asserts ops values
opsAdds = (Double) ops.get(CdcrParams.COUNTER_ADDS);
opsDeletes = (Double) ops.get(CdcrParams.COUNTER_DELETES);
assertTrue(opsAll > 0);
assertTrue(opsAdds > 0);
assertTrue(opsDeletes > 0);
deleteCollections();
}
@Test
public void testTargetCollectionNotAvailable() throws Exception {
createCollections();
// send start action to first shard
CdcrTestsUtil.cdcrStart(sourceSolrClient);
assertNotSame(null, CdcrTestsUtil.waitForClusterToSync
(sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound(), targetSolrClient));
// sleep for a bit to ensure that replicator threads are started
Thread.sleep(3000);
target.deleteAllCollections();
CdcrTestsUtil.indexRandomDocs(6, sourceSolrClient);
assertEquals(6L, sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound());
// we need to wait until the replicator thread is triggered
int cnt = 15; // timeout after 15 seconds
AssertionError lastAssertionError = null;
while (cnt > 0) {
try {
QueryResponse rsp = CdcrTestsUtil.invokeCdcrAction(sourceSolrClient, CdcrParams.CdcrAction.ERRORS);
@SuppressWarnings({"rawtypes"})
NamedList collections = (NamedList) ((NamedList) rsp.getResponse().get(CdcrParams.ERRORS)).getVal(0);
@SuppressWarnings({"rawtypes"})
NamedList errors = (NamedList) collections.get(TARGET_COLLECTION);
assertTrue(0 < (Long) errors.get(CdcrParams.CONSECUTIVE_ERRORS));
@SuppressWarnings({"rawtypes"})
NamedList lastErrors = (NamedList) errors.get(CdcrParams.LAST);
assertNotNull(lastErrors);
assertTrue(0 < lastErrors.size());
deleteCollections();
return;
} catch (AssertionError e) {
lastAssertionError = e;
cnt--;
Thread.sleep(1000);
}
}
deleteCollections();
throw new AssertionError("Timeout while trying to assert replication errors", lastAssertionError);
}
@Test
public void testReplicationStartStop() throws Exception {
createCollections();
CdcrTestsUtil.indexRandomDocs(10, sourceSolrClient);
CdcrTestsUtil.cdcrStart(sourceSolrClient);
assertEquals(10, CdcrTestsUtil.waitForClusterToSync
(sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound(), targetSolrClient));
CdcrTestsUtil.cdcrStop(sourceSolrClient);
CdcrTestsUtil.indexRandomDocs(110, sourceSolrClient);
// Start again CDCR, the source cluster should reinitialise its log readers
// with the latest checkpoints
CdcrTestsUtil.cdcrRestart(sourceSolrClient);
assertEquals(110, CdcrTestsUtil.waitForClusterToSync
(sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound(), targetSolrClient));
deleteCollections();
}
/**
* Check that batch updates with deletes
*/
@Test
public void testBatchAddsWithDelete() throws Exception {
createCollections();
// Start CDCR
CdcrTestsUtil.cdcrRestart(sourceSolrClient);
// Index 50 documents
CdcrTestsUtil.indexRandomDocs(50, sourceSolrClient);
// Delete 10 documents: 10-19
List<String> ids = new ArrayList<>();
for (int id = 10; id < 20; id++) {
ids.add(Integer.toString(id));
}
sourceSolrClient.deleteById(ids, 10);
CdcrTestsUtil.indexRandomDocs(50, 60, sourceSolrClient);
// Delete 1 document: 50
ids = new ArrayList<>();
ids.add(Integer.toString(50));
sourceSolrClient.deleteById(ids, 10);
CdcrTestsUtil.indexRandomDocs(60, 70, sourceSolrClient);
assertEquals(59, CdcrTestsUtil.waitForClusterToSync(59, sourceSolrClient));
assertEquals(59, CdcrTestsUtil.waitForClusterToSync
(sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound(), targetSolrClient));
deleteCollections();
}
/**
* Checks that batches are correctly constructed when batch boundaries are reached.
*/
@Test
public void testBatchBoundaries() throws Exception {
createCollections();
// Start CDCR
CdcrTestsUtil.cdcrRestart(sourceSolrClient);
log.info("Indexing documents");
CdcrTestsUtil.indexRandomDocs(1000, sourceSolrClient);
assertEquals(1000, CdcrTestsUtil.waitForClusterToSync
(sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound(), targetSolrClient));
deleteCollections();
}
/**
* Check resilience of replication with delete by query executed on targets
*/
@Test
public void testResilienceWithDeleteByQueryOnTarget() throws Exception {
createCollections();
// Start CDCR
CdcrTestsUtil.cdcrRestart(sourceSolrClient);
CdcrTestsUtil.indexRandomDocs(50, sourceSolrClient);
assertEquals(50, CdcrTestsUtil.waitForClusterToSync
(sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound(), targetSolrClient));
sourceSolrClient.deleteByQuery(ALL_Q, 1);
assertEquals(0, CdcrTestsUtil.waitForClusterToSync(0, sourceSolrClient));
assertEquals(0, CdcrTestsUtil.waitForClusterToSync
(sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound(), targetSolrClient));
CdcrTestsUtil.indexRandomDocs(51, 101, sourceSolrClient);
assertEquals(50, CdcrTestsUtil.waitForClusterToSync
(sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound(), targetSolrClient));
targetSolrClient.deleteByQuery(ALL_Q, 1);
assertEquals(50, sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound());
assertEquals(0, CdcrTestsUtil.waitForClusterToSync(0, targetSolrClient));
CdcrTestsUtil.indexRandomDocs(102, 152, sourceSolrClient);
assertEquals(100, sourceSolrClient.query(new SolrQuery(ALL_Q)).getResults().getNumFound());
assertEquals(50, CdcrTestsUtil.waitForClusterToSync(50, targetSolrClient));
deleteCollections();
}
private void createSourceCollection() throws Exception {
source.uploadConfigSet(configset(SOURCE_COLLECTION), SOURCE_COLLECTION);
CollectionAdminRequest.createCollection(SOURCE_COLLECTION, SOURCE_COLLECTION, 1, 1)
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(source.getSolrClient());
Thread.sleep(1000);
sourceSolrClient = source.getSolrClient();
sourceSolrClient.setDefaultCollection(SOURCE_COLLECTION);
}
private void createTargetCollection() throws Exception {
target.uploadConfigSet(configset(TARGET_COLLECTION), TARGET_COLLECTION);
CollectionAdminRequest.createCollection(TARGET_COLLECTION, TARGET_COLLECTION, 1, 1)
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(target.getSolrClient());
Thread.sleep(1000);
targetSolrClient = target.getSolrClient();
targetSolrClient.setDefaultCollection(TARGET_COLLECTION);
}
private void deleteSourceCollection() throws Exception {
source.deleteAllCollections();
}
private void deleteTargetcollection() throws Exception {
target.deleteAllCollections();
}
private void createCollections() throws Exception {
createTargetCollection();
createSourceCollection();
}
private void deleteCollections() throws Exception {
deleteSourceCollection();
deleteTargetcollection();
}
}

View File

@ -1,332 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cloud.cdcr;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.lucene.util.LuceneTestCase.Nightly;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is testing the cdcr extension to the {@link org.apache.solr.handler.ReplicationHandler} and
* {@link org.apache.solr.handler.IndexFetcher}.
*/
@Nightly
public class CdcrReplicationHandlerTest extends BaseCdcrDistributedZkTest {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Override
public void distribSetUp() throws Exception {
schemaString = "schema15.xml"; // we need a string id
createTargetCollection = false; // we do not need the target cluster
shardCount = 1; // we need only one shard
// we need a persistent directory, otherwise the UpdateHandler will erase existing tlog files after restarting a node
System.setProperty("solr.directoryFactory", "solr.StandardDirectoryFactory");
super.distribSetUp();
}
/**
* Test the scenario where the follower is killed from the start. The replication
* strategy should fetch all the missing tlog files from the leader.
*/
@Test
@ShardsFixed(num = 2)
public void testFullReplication() throws Exception {
List<CloudJettyRunner> followers = this.getShardToFollowerJetty(SOURCE_COLLECTION, SHARD1);
followers.get(0).jetty.stop();
for (int i = 0; i < 10; i++) {
List<SolrInputDocument> docs = new ArrayList<>();
for (int j = i * 10; j < (i * 10) + 10; j++) {
docs.add(getDoc(id, Integer.toString(j)));
}
index(SOURCE_COLLECTION, docs);
}
assertNumDocs(100, SOURCE_COLLECTION);
// Restart the follower node to trigger Replication strategy
this.restartServer(followers.get(0));
this.assertUpdateLogsEquals(SOURCE_COLLECTION, 10);
}
/**
* Test the scenario where the follower is killed before receiving all the documents. The replication
* strategy should fetch all the missing tlog files from the leader.
*/
@Test
@ShardsFixed(num = 2)
public void testPartialReplication() throws Exception {
for (int i = 0; i < 5; i++) {
List<SolrInputDocument> docs = new ArrayList<>();
for (int j = i * 20; j < (i * 20) + 20; j++) {
docs.add(getDoc(id, Integer.toString(j)));
}
index(SOURCE_COLLECTION, docs);
}
List<CloudJettyRunner> followers = this.getShardToFollowerJetty(SOURCE_COLLECTION, SHARD1);
followers.get(0).jetty.stop();
for (int i = 5; i < 10; i++) {
List<SolrInputDocument> docs = new ArrayList<>();
for (int j = i * 20; j < (i * 20) + 20; j++) {
docs.add(getDoc(id, Integer.toString(j)));
}
index(SOURCE_COLLECTION, docs);
}
assertNumDocs(200, SOURCE_COLLECTION);
// Restart the follower node to trigger Replication strategy
this.restartServer(followers.get(0));
// at this stage, the follower should have replicated the 5 missing tlog files
this.assertUpdateLogsEquals(SOURCE_COLLECTION, 10);
}
/**
* Test the scenario where the follower is killed before receiving a commit. This creates a truncated tlog
* file on the follower node. The replication strategy should detect this truncated file, and fetch the
* non-truncated file from the leader.
*/
@Test
@ShardsFixed(num = 2)
public void testPartialReplicationWithTruncatedTlog() throws Exception {
CloudSolrClient client = createCloudClient(SOURCE_COLLECTION);
List<CloudJettyRunner> followers = this.getShardToFollowerJetty(SOURCE_COLLECTION, SHARD1);
try {
for (int i = 0; i < 10; i++) {
for (int j = i * 20; j < (i * 20) + 20; j++) {
client.add(getDoc(id, Integer.toString(j)));
// Stop the follower in the middle of a batch to create a truncated tlog on the follower
if (j == 45) {
followers.get(0).jetty.stop();
}
}
commit(SOURCE_COLLECTION);
}
} finally {
client.close();
}
assertNumDocs(200, SOURCE_COLLECTION);
// Restart the follower node to trigger Replication recovery
this.restartServer(followers.get(0));
// at this stage, the follower should have replicated the 5 missing tlog files
this.assertUpdateLogsEquals(SOURCE_COLLECTION, 10);
}
/**
* Test the scenario where the follower first recovered with a PeerSync strategy, then with a Replication strategy.
* The PeerSync strategy will generate a single tlog file for all the missing updates on the follower node.
* If a Replication strategy occurs at a later stage, it should remove this tlog file generated by PeerSync
* and fetch the corresponding tlog files from the leader.
*/
@Test
@ShardsFixed(num = 2)
public void testPartialReplicationAfterPeerSync() throws Exception {
for (int i = 0; i < 5; i++) {
List<SolrInputDocument> docs = new ArrayList<>();
for (int j = i * 10; j < (i * 10) + 10; j++) {
docs.add(getDoc(id, Integer.toString(j)));
}
index(SOURCE_COLLECTION, docs);
}
List<CloudJettyRunner> followers = this.getShardToFollowerJetty(SOURCE_COLLECTION, SHARD1);
followers.get(0).jetty.stop();
for (int i = 5; i < 10; i++) {
List<SolrInputDocument> docs = new ArrayList<>();
for (int j = i * 10; j < (i * 10) + 10; j++) {
docs.add(getDoc(id, Integer.toString(j)));
}
index(SOURCE_COLLECTION, docs);
}
assertNumDocs(100, SOURCE_COLLECTION);
// Restart the follower node to trigger PeerSync recovery
// (the update windows between leader and follower is small enough)
this.restartServer(followers.get(0));
followers.get(0).jetty.stop();
for (int i = 10; i < 15; i++) {
List<SolrInputDocument> docs = new ArrayList<>();
for (int j = i * 20; j < (i * 20) + 20; j++) {
docs.add(getDoc(id, Integer.toString(j)));
}
index(SOURCE_COLLECTION, docs);
}
// restart the follower node to trigger Replication recovery
this.restartServer(followers.get(0));
// at this stage, the follower should have replicated the 5 missing tlog files
this.assertUpdateLogsEquals(SOURCE_COLLECTION, 15);
}
/**
* Test the scenario where the follower is killed while the leader is still receiving updates.
* The follower should buffer updates while in recovery, then replay them at the end of the recovery.
* If updates were properly buffered and replayed, then the follower should have the same number of documents
* than the leader. This checks if cdcr tlog replication interferes with buffered updates - SOLR-8263.
*/
@Test
@ShardsFixed(num = 2)
public void testReplicationWithBufferedUpdates() throws Exception {
List<CloudJettyRunner> followers = this.getShardToFollowerJetty(SOURCE_COLLECTION, SHARD1);
AtomicInteger numDocs = new AtomicInteger(0);
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new SolrNamedThreadFactory("cdcr-test-update-scheduler"));
executor.scheduleWithFixedDelay(new UpdateThread(numDocs), 10, 10, TimeUnit.MILLISECONDS);
// Restart the follower node to trigger Replication strategy
this.restartServer(followers.get(0));
// shutdown the update thread and wait for its completion
executor.shutdown();
executor.awaitTermination(500, TimeUnit.MILLISECONDS);
// check that we have the expected number of documents in the cluster
assertNumDocs(numDocs.get(), SOURCE_COLLECTION);
// check that we have the expected number of documents on the follower
assertNumDocs(numDocs.get(), followers.get(0));
}
private void assertNumDocs(int expectedNumDocs, CloudJettyRunner jetty)
throws InterruptedException, IOException, SolrServerException {
SolrClient client = createNewSolrServer(jetty.url);
try {
int cnt = 30; // timeout after 15 seconds
AssertionError lastAssertionError = null;
while (cnt > 0) {
try {
assertEquals(expectedNumDocs, client.query(new SolrQuery("*:*")).getResults().getNumFound());
return;
}
catch (AssertionError e) {
lastAssertionError = e;
cnt--;
Thread.sleep(500);
}
}
throw new AssertionError("Timeout while trying to assert number of documents @ " + jetty.url, lastAssertionError);
} finally {
client.close();
}
}
private class UpdateThread implements Runnable {
private AtomicInteger numDocs;
private UpdateThread(AtomicInteger numDocs) {
this.numDocs = numDocs;
}
@Override
public void run() {
try {
List<SolrInputDocument> docs = new ArrayList<>();
for (int j = numDocs.get(); j < (numDocs.get() + 10); j++) {
docs.add(getDoc(id, Integer.toString(j)));
}
index(SOURCE_COLLECTION, docs);
numDocs.getAndAdd(10);
if (log.isInfoEnabled()) {
log.info("Sent batch of {} updates - numDocs:{}", docs.size(), numDocs);
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private List<CloudJettyRunner> getShardToFollowerJetty(String collection, String shard) {
List<CloudJettyRunner> jetties = new ArrayList<>(shardToJetty.get(collection).get(shard));
CloudJettyRunner leader = shardToLeaderJetty.get(collection).get(shard);
jetties.remove(leader);
return jetties;
}
/**
* Asserts that the update logs are in sync between the leader and follower. The leader and the followers
* must have identical tlog files.
*/
protected void assertUpdateLogsEquals(String collection, int numberOfTLogs) throws Exception {
CollectionInfo info = collectInfo(collection);
Map<String, List<CollectionInfo.CoreInfo>> shardToCoresMap = info.getShardToCoresMap();
for (String shard : shardToCoresMap.keySet()) {
Map<Long, Long> leaderFilesMeta = this.getFilesMeta(info.getLeader(shard).ulogDir);
Map<Long, Long> followerFilesMeta = this.getFilesMeta(info.getReplicas(shard).get(0).ulogDir);
assertEquals("Incorrect number of tlog files on the leader", numberOfTLogs, leaderFilesMeta.size());
assertEquals("Incorrect number of tlog files on the follower", numberOfTLogs, followerFilesMeta.size());
for (Long leaderFileVersion : leaderFilesMeta.keySet()) {
assertTrue("Follower is missing a tlog for version " + leaderFileVersion, followerFilesMeta.containsKey(leaderFileVersion));
assertEquals("Follower's tlog file size differs for version " + leaderFileVersion, leaderFilesMeta.get(leaderFileVersion), followerFilesMeta.get(leaderFileVersion));
}
}
}
private Map<Long, Long> getFilesMeta(String dir) {
File file = new File(dir);
if (!file.isDirectory()) {
assertTrue("Path to tlog " + dir + " does not exists or it's not a directory.", false);
}
Map<Long, Long> filesMeta = new HashMap<>();
for (File tlogFile : file.listFiles()) {
filesMeta.put(Math.abs(Long.parseLong(tlogFile.getName().substring(tlogFile.getName().lastIndexOf('.') + 1))), tlogFile.length());
}
return filesMeta;
}
}

View File

@ -1,183 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cloud.cdcr;
import java.util.Arrays;
import com.google.common.collect.ImmutableMap;
import org.apache.lucene.util.LuceneTestCase.Nightly;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.handler.CdcrParams;
import org.junit.Test;
@Nightly
public class CdcrRequestHandlerTest extends BaseCdcrDistributedZkTest {
@Override
public void distribSetUp() throws Exception {
schemaString = "schema15.xml"; // we need a string id
createTargetCollection = false; // we do not need the target cluster
super.distribSetUp();
}
// check that the life-cycle state is properly synchronised across nodes
@Test
@ShardsFixed(num = 2)
public void testLifeCycleActions() throws Exception {
// check initial status
this.assertState(SOURCE_COLLECTION, CdcrParams.ProcessState.STOPPED, CdcrParams.BufferState.ENABLED);
// send start action to first shard
@SuppressWarnings({"rawtypes"})
NamedList rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.START);
@SuppressWarnings({"rawtypes"})
NamedList status = (NamedList) rsp.get(CdcrParams.CdcrAction.STATUS.toLower());
assertEquals(CdcrParams.ProcessState.STARTED.toLower(), status.get(CdcrParams.ProcessState.getParam()));
// check status
this.assertState(SOURCE_COLLECTION, CdcrParams.ProcessState.STARTED, CdcrParams.BufferState.ENABLED);
// Restart the leader of shard 1
this.restartServer(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1));
// check status - the node that died should have picked up the original state
this.assertState(SOURCE_COLLECTION, CdcrParams.ProcessState.STARTED, CdcrParams.BufferState.ENABLED);
// send stop action to second shard
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.STOP);
status = (NamedList) rsp.get(CdcrParams.CdcrAction.STATUS.toLower());
assertEquals(CdcrParams.ProcessState.STOPPED.toLower(), status.get(CdcrParams.ProcessState.getParam()));
// check status
this.assertState(SOURCE_COLLECTION, CdcrParams.ProcessState.STOPPED, CdcrParams.BufferState.ENABLED);
}
// check the checkpoint API
@Test
@ShardsFixed(num = 2)
public void testCheckpointActions() throws Exception {
// initial request on an empty index, must return -1
@SuppressWarnings({"rawtypes"})
NamedList rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.COLLECTIONCHECKPOINT);
assertEquals(-1l, rsp.get(CdcrParams.CHECKPOINT));
index(SOURCE_COLLECTION, getDoc(id, "a","test_i_dvo",10)); // shard 2
// only one document indexed in shard 2, the checkpoint must be still -1
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.COLLECTIONCHECKPOINT);
assertEquals(-1l, rsp.get(CdcrParams.CHECKPOINT));
index(SOURCE_COLLECTION, getDoc(id, "b")); // shard 1
// a second document indexed in shard 1, the checkpoint must come from shard 2
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.COLLECTIONCHECKPOINT);
long checkpoint1 = (Long) rsp.get(CdcrParams.CHECKPOINT);
long expected = (Long) invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.SHARDCHECKPOINT).get(CdcrParams.CHECKPOINT);
assertEquals(expected, checkpoint1);
index(SOURCE_COLLECTION, getDoc(id, "c")); // shard 1
// a third document indexed in shard 1, the checkpoint must still come from shard 2
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.COLLECTIONCHECKPOINT);
assertEquals(checkpoint1, rsp.get(CdcrParams.CHECKPOINT));
index(SOURCE_COLLECTION, getDoc(id, "d")); // shard 2
// a fourth document indexed in shard 2, the checkpoint must come from shard 1
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.COLLECTIONCHECKPOINT);
long checkpoint2 = (Long) rsp.get(CdcrParams.CHECKPOINT);
expected = (Long) invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.SHARDCHECKPOINT).get(CdcrParams.CHECKPOINT);
assertEquals(expected, checkpoint2);
// send a delete by id
long pre_op = (Long) invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.SHARDCHECKPOINT).get(CdcrParams.CHECKPOINT);
deleteById(SOURCE_COLLECTION, Arrays.asList(new String[]{"c"})); //shard1
// document deleted in shard1, checkpoint should come from shard2
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.COLLECTIONCHECKPOINT);
long checkpoint3 = (Long) rsp.get(CdcrParams.CHECKPOINT);
expected = (Long) invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.SHARDCHECKPOINT).get(CdcrParams.CHECKPOINT);
assertEquals(pre_op, expected);
assertEquals(expected, checkpoint3);
// send a in-place update
SolrInputDocument in_place_doc = new SolrInputDocument();
in_place_doc.setField(id, "a");
in_place_doc.setField("test_i_dvo", ImmutableMap.of("inc", 10)); //shard2
index(SOURCE_COLLECTION, in_place_doc);
// document updated in shard2, checkpoint should come from shard1
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.COLLECTIONCHECKPOINT);
long checkpoint4 = (Long) rsp.get(CdcrParams.CHECKPOINT);
expected = (Long) invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.SHARDCHECKPOINT).get(CdcrParams.CHECKPOINT);
assertEquals(expected, checkpoint4);
// send a delete by query
deleteByQuery(SOURCE_COLLECTION, "*:*");
// all the checkpoints must come from the DBQ
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.COLLECTIONCHECKPOINT);
long checkpoint5= (Long) rsp.get(CdcrParams.CHECKPOINT);
assertTrue(checkpoint5 > 0); // ensure that checkpoints from deletes are in absolute form
checkpoint5 = (Long) invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.SHARDCHECKPOINT).get(CdcrParams.CHECKPOINT);
assertTrue(checkpoint5 > 0); // ensure that checkpoints from deletes are in absolute form
checkpoint5 = (Long) invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.SHARDCHECKPOINT).get(CdcrParams.CHECKPOINT);
assertTrue(checkpoint5 > 0); // ensure that checkpoints from deletes are in absolute form
// replication never started, lastProcessedVersion should be -1 for both shards
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.LASTPROCESSEDVERSION);
long lastVersion = (Long) rsp.get(CdcrParams.LAST_PROCESSED_VERSION);
assertEquals(-1l, lastVersion);
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.LASTPROCESSEDVERSION);
lastVersion = (Long) rsp.get(CdcrParams.LAST_PROCESSED_VERSION);
assertEquals(-1l, lastVersion);
}
// check that the buffer state is properly synchronised across nodes
@Test
@ShardsFixed(num = 2)
public void testBufferActions() throws Exception {
// check initial status
this.assertState(SOURCE_COLLECTION, CdcrParams.ProcessState.STOPPED, CdcrParams.BufferState.ENABLED);
// send disable buffer action to first shard
@SuppressWarnings({"rawtypes"})
NamedList rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1), CdcrParams.CdcrAction.DISABLEBUFFER);
@SuppressWarnings({"rawtypes"})
NamedList status = (NamedList) rsp.get(CdcrParams.CdcrAction.STATUS.toLower());
assertEquals(CdcrParams.BufferState.DISABLED.toLower(), status.get(CdcrParams.BufferState.getParam()));
// check status
this.assertState(SOURCE_COLLECTION, CdcrParams.ProcessState.STOPPED, CdcrParams.BufferState.DISABLED);
// Restart the leader of shard 1
this.restartServer(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD1));
// check status
this.assertState(SOURCE_COLLECTION, CdcrParams.ProcessState.STOPPED, CdcrParams.BufferState.DISABLED);
// send enable buffer action to second shard
rsp = invokeCdcrAction(shardToLeaderJetty.get(SOURCE_COLLECTION).get(SHARD2), CdcrParams.CdcrAction.ENABLEBUFFER);
status = (NamedList) rsp.get(CdcrParams.CdcrAction.STATUS.toLower());
assertEquals(CdcrParams.BufferState.ENABLED.toLower(), status.get(CdcrParams.BufferState.getParam()));
// check status
this.assertState(SOURCE_COLLECTION, CdcrParams.ProcessState.STOPPED, CdcrParams.BufferState.ENABLED);
}
}

View File

@ -1,274 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cloud.cdcr;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.LinkedHashMap;
import java.util.concurrent.TimeUnit;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.MiniSolrCloudCluster;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.TimeSource;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.CdcrParams;
import org.apache.solr.util.TimeOut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CdcrTestsUtil extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected static void cdcrRestart(CloudSolrClient client) throws SolrServerException, IOException {
cdcrStop(client);
cdcrStart(client);
}
protected static void cdcrStart(CloudSolrClient client) throws SolrServerException, IOException {
QueryResponse response = invokeCdcrAction(client, CdcrParams.CdcrAction.START);
assertEquals("started", ((NamedList) response.getResponse().get("status")).get("process"));
}
protected static void cdcrStop(CloudSolrClient client) throws SolrServerException, IOException {
QueryResponse response = invokeCdcrAction(client, CdcrParams.CdcrAction.STOP);
assertEquals("stopped", ((NamedList) response.getResponse().get("status")).get("process"));
}
protected static void cdcrEnableBuffer(CloudSolrClient client) throws IOException, SolrServerException {
QueryResponse response = invokeCdcrAction(client, CdcrParams.CdcrAction.ENABLEBUFFER);
assertEquals("enabled", ((NamedList) response.getResponse().get("status")).get("buffer"));
}
protected static void cdcrDisableBuffer(CloudSolrClient client) throws IOException, SolrServerException {
QueryResponse response = invokeCdcrAction(client, CdcrParams.CdcrAction.DISABLEBUFFER);
assertEquals("disabled", ((NamedList) response.getResponse().get("status")).get("buffer"));
}
protected static QueryResponse invokeCdcrAction(CloudSolrClient client, CdcrParams.CdcrAction action) throws IOException, SolrServerException {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CommonParams.QT, "/cdcr");
params.set(CommonParams.ACTION, action.toLower());
return client.query(params);
}
protected static QueryResponse getCdcrQueue(CloudSolrClient client) throws SolrServerException, IOException {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CommonParams.QT, "/cdcr");
params.set(CommonParams.ACTION, CdcrParams.QUEUES);
return client.query(params);
}
protected static Object getFingerPrintMaxVersion(CloudSolrClient client, String shardNames, int numDocs) throws SolrServerException, IOException, InterruptedException {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CommonParams.QT, "/get");
params.set("fingerprint", true);
params.set("shards", shardNames);
params.set("getVersions", numDocs);
QueryResponse response = null;
long start = System.nanoTime();
while (System.nanoTime() - start <= TimeUnit.NANOSECONDS.convert(20, TimeUnit.SECONDS)) {
response = client.query(params);
if (response.getResponse() != null && response.getResponse().get("fingerprint") != null) {
return (long) ((LinkedHashMap) response.getResponse().get("fingerprint")).get("maxVersionEncountered");
}
Thread.sleep(200);
}
log.error("maxVersionEncountered not found for client : {} in 20 attempts", client);
return null;
}
protected static long waitForClusterToSync(long numDocs, CloudSolrClient clusterSolrClient) throws Exception {
return waitForClusterToSync((int) numDocs, clusterSolrClient, "*:*");
}
protected static long waitForClusterToSync(int numDocs, CloudSolrClient clusterSolrClient) throws Exception {
return waitForClusterToSync(numDocs, clusterSolrClient, "*:*");
}
protected static long waitForClusterToSync(int numDocs, CloudSolrClient clusterSolrClient, String query) throws Exception {
long start = System.nanoTime();
QueryResponse response = null;
while (System.nanoTime() - start <= TimeUnit.NANOSECONDS.convert(120, TimeUnit.SECONDS)) {
clusterSolrClient.commit();
response = clusterSolrClient.query(new SolrQuery(query));
if (response.getResults().getNumFound() == numDocs) {
break;
}
Thread.sleep(1000);
}
return response != null ? response.getResults().getNumFound() : 0;
}
protected static boolean assertShardInSync(String collection, String shard, CloudSolrClient client) throws IOException, SolrServerException {
TimeOut waitTimeOut = new TimeOut(30, TimeUnit.SECONDS, TimeSource.NANO_TIME);
DocCollection docCollection = client.getZkStateReader().getClusterState().getCollection(collection);
Slice correctSlice = null;
for (Slice slice : docCollection.getSlices()) {
if (shard.equals(slice.getName())) {
correctSlice = slice;
break;
}
}
assertNotNull(correctSlice);
long leaderDocCount;
try (HttpSolrClient leaderClient = new HttpSolrClient.Builder(correctSlice.getLeader().getCoreUrl()).withHttpClient(client.getHttpClient()).build()) {
leaderDocCount = leaderClient.query(new SolrQuery("*:*").setParam("distrib", "false")).getResults().getNumFound();
}
while (!waitTimeOut.hasTimedOut()) {
int replicasInSync = 0;
for (Replica replica : correctSlice.getReplicas()) {
try (HttpSolrClient leaderClient = new HttpSolrClient.Builder(replica.getCoreUrl()).withHttpClient(client.getHttpClient()).build()) {
long replicaDocCount = leaderClient.query(new SolrQuery("*:*").setParam("distrib", "false")).getResults().getNumFound();
if (replicaDocCount == leaderDocCount) replicasInSync++;
}
}
if (replicasInSync == correctSlice.getReplicas().size()) {
return true;
}
}
return false;
}
public static void indexRandomDocs(Integer start, Integer count, CloudSolrClient solrClient) throws Exception {
// ADD operation on cluster 1
int docs = 0;
if (count == 0) {
docs = (TEST_NIGHTLY ? 100 : 10);
} else {
docs = count;
}
for (int k = start; k < docs; k++) {
UpdateRequest req = new UpdateRequest();
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", k);
req.add(doc);
req.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
req.process(solrClient);
}
}
public static void indexRandomDocs(Integer count, CloudSolrClient solrClient) throws Exception {
indexRandomDocs(0, count, solrClient);
}
public static void index(MiniSolrCloudCluster cluster, String collection, SolrInputDocument doc, boolean doCommit) throws IOException, SolrServerException {
CloudSolrClient client = createCloudClient(cluster, collection);
try {
client.add(doc);
if (doCommit) {
client.commit(true, true);
} else {
client.commit(true, false);
}
} finally {
client.close();
}
}
public static void index(MiniSolrCloudCluster cluster, String collection, SolrInputDocument doc) throws IOException, SolrServerException {
index(cluster, collection, doc, false);
}
public static CloudSolrClient createCloudClient(MiniSolrCloudCluster cluster, String defaultCollection) {
CloudSolrClient server = getCloudSolrClient(cluster.getZkServer().getZkAddress(), random().nextBoolean());
if (defaultCollection != null) server.setDefaultCollection(defaultCollection);
return server;
}
public static void restartClusterNode(MiniSolrCloudCluster cluster, String collection, int index) throws Exception {
System.setProperty("collection", collection);
restartNode(cluster.getJettySolrRunner(index));
System.clearProperty("collection");
}
public static void restartClusterNodes(MiniSolrCloudCluster cluster, String collection) throws Exception {
System.setProperty("collection", collection);
for (JettySolrRunner jetty : cluster.getJettySolrRunners()) {
restartNode(jetty);
}
System.clearProperty("collection");
}
public static void restartNode(JettySolrRunner jetty) throws Exception {
jetty.stop();
jetty.start();
Thread.sleep(10000);
}
public static int numberOfFiles(String dir) {
File file = new File(dir);
if (!file.isDirectory()) {
assertTrue("Path to tlog " + dir + " does not exists or it's not a directory.", false);
}
if (log.isDebugEnabled()) {
log.debug("Update log dir {} contains: {}", dir, file.listFiles());
}
return file.listFiles().length;
}
public static int getNumberOfTlogFilesOnReplicas(MiniSolrCloudCluster cluster) throws Exception {
int count = 0;
for (JettySolrRunner jetty : cluster.getJettySolrRunners()) {
for (SolrCore core : jetty.getCoreContainer().getCores()) {
count += numberOfFiles(core.getUlogDir() + "/tlog");
}
}
return count;
}
public static String getNonLeaderNode(MiniSolrCloudCluster cluster, String collection) throws Exception {
String leaderNode = getLeaderNode(cluster, collection);
for (JettySolrRunner jetty : cluster.getJettySolrRunners()) {
if (!jetty.getNodeName().equals(leaderNode)) {
return jetty.getNodeName();
}
}
return cluster.getJettySolrRunners().get(0).getNodeName();
}
public static String getLeaderNode(MiniSolrCloudCluster cluster, String collection) throws Exception {
for (Replica replica : cluster.getSolrClient().getClusterStateProvider().getCollection(collection).getReplicas()) {
if (cluster.getSolrClient().getClusterStateProvider().getCollection(collection).getLeader("shard1") == replica) {
return replica.getNodeName();
}
}
return "";
}
}

View File

@ -1,307 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cloud.cdcr;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.update.processor.CdcrUpdateProcessor;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CdcrVersionReplicationTest extends BaseCdcrDistributedZkTest {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final String vfield = CommonParams.VERSION_FIELD;
SolrClient solrServer;
public CdcrVersionReplicationTest() {
schemaString = "schema15.xml"; // we need a string id
super.createTargetCollection = false;
}
SolrClient createClientRandomly() throws Exception {
int r = random().nextInt(100);
// testing the smart cloud client (requests to leaders) is more important than testing the forwarding logic
if (r < 80) {
return createCloudClient(SOURCE_COLLECTION);
}
if (r < 90) {
return createNewSolrServer(shardToJetty.get(SOURCE_COLLECTION).get(SHARD1).get(random().nextInt(2)).url);
}
return createNewSolrServer(shardToJetty.get(SOURCE_COLLECTION).get(SHARD2).get(random().nextInt(2)).url);
}
@Test
@ShardsFixed(num = 4)
public void testCdcrDocVersions() throws Exception {
SolrClient client = createClientRandomly();
try {
handle.clear();
handle.put("timestamp", SKIPVAL);
doTestCdcrDocVersions(client);
commit(SOURCE_COLLECTION); // work arround SOLR-5628
} finally {
client.close();
}
}
private void doTestCdcrDocVersions(SolrClient solrClient) throws Exception {
this.solrServer = solrClient;
log.info("### STARTING doCdcrTestDocVersions - Add commands, client: {}", solrClient);
vadd("doc1", 10, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "10");
vadd("doc2", 11, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "11");
vadd("doc3", 10, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "10");
vadd("doc4", 11, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "11");
commit(SOURCE_COLLECTION);
// versions are preserved and verifiable both by query and by real-time get
doQuery(solrClient, "doc1,10,doc2,11,doc3,10,doc4,11", "q", "*:*");
doRealTimeGet("doc1,doc2,doc3,doc4", "10,11,10,11");
vadd("doc1", 5, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "5");
vadd("doc2", 10, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "10");
vadd("doc3", 9, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "9");
vadd("doc4", 8, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "8");
// lower versions are ignored
doRealTimeGet("doc1,doc2,doc3,doc4", "10,11,10,11");
vadd("doc1", 12, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "12");
vadd("doc2", 12, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "12");
vadd("doc3", 12, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "12");
vadd("doc4", 12, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "12");
// higher versions are accepted
doRealTimeGet("doc1,doc2,doc3,doc4", "12,12,12,12");
// non-cdcr update requests throw a version conflict exception for non-equal versions (optimistic locking feature)
vaddFail("doc1", 13, 409);
vaddFail("doc2", 13, 409);
vaddFail("doc3", 13, 409);
commit(SOURCE_COLLECTION);
// versions are still as they were
doQuery(solrClient, "doc1,12,doc2,12,doc3,12,doc4,12", "q", "*:*");
// query all shard replicas individually
doQueryShardReplica(SHARD1, "doc1,12,doc2,12,doc3,12,doc4,12", "q", "*:*");
doQueryShardReplica(SHARD2, "doc1,12,doc2,12,doc3,12,doc4,12", "q", "*:*");
// optimistic locking update
vadd("doc4", 12);
commit(SOURCE_COLLECTION);
QueryResponse rsp = solrClient.query(params("qt", "/get", "ids", "doc4"));
long version = (long) rsp.getResults().get(0).get(vfield);
// update accepted and a new version number was generated
assertTrue(version > 1_000_000_000_000l);
log.info("### STARTING doCdcrTestDocVersions - Delete commands");
// send a delete update with an older version number
vdelete("doc1", 5, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "5");
// must ignore the delete
doRealTimeGet("doc1", "12");
// send a delete update with a higher version number
vdelete("doc1", 13, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "13");
// must be deleted
doRealTimeGet("doc1", "");
// send a delete update with a higher version number
vdelete("doc4", version + 1, CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, "" + (version + 1));
// must be deleted
doRealTimeGet("doc4", "");
commit(SOURCE_COLLECTION);
// query each shard replica individually
doQueryShardReplica(SHARD1, "doc2,12,doc3,12", "q", "*:*");
doQueryShardReplica(SHARD2, "doc2,12,doc3,12", "q", "*:*");
// version conflict thanks to optimistic locking
if (solrClient instanceof CloudSolrClient) // TODO: it seems that optimistic locking doesn't work with forwarding, test with shard2 client
vdeleteFail("doc2", 50, 409);
// cleanup after ourselves for the next run
// deleteByQuery should work as usual with the CDCR_UPDATE param
doDeleteByQuery("id:doc*", CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, Long.toString(1));
commit(SOURCE_COLLECTION);
// deleteByQuery with a version lower than anything else should have no effect
doQuery(solrClient, "doc2,12,doc3,12", "q", "*:*");
doDeleteByQuery("id:doc*", CdcrUpdateProcessor.CDCR_UPDATE, "", vfield, Long.toString(51));
commit(SOURCE_COLLECTION);
// deleteByQuery with a version higher than everything else should delete all remaining docs
doQuery(solrClient, "", "q", "*:*");
// check that replicas are as expected too
doQueryShardReplica(SHARD1, "", "q", "*:*");
doQueryShardReplica(SHARD2, "", "q", "*:*");
}
// ------------------ auxiliary methods ------------------
void doQueryShardReplica(String shard, String expectedDocs, String... queryParams) throws Exception {
for (CloudJettyRunner jetty : shardToJetty.get(SOURCE_COLLECTION).get(shard)) {
doQuery(jetty.client, expectedDocs, queryParams);
}
}
void vdelete(String id, long version, String... params) throws Exception {
UpdateRequest req = new UpdateRequest();
req.deleteById(id);
req.setParam(vfield, Long.toString(version));
for (int i = 0; i < params.length; i += 2) {
req.setParam(params[i], params[i + 1]);
}
solrServer.request(req);
}
void vdeleteFail(String id, long version, int errCode, String... params) throws Exception {
boolean failed = false;
try {
vdelete(id, version, params);
} catch (SolrException e) {
failed = true;
if (e.getCause() instanceof SolrException && e.getCause() != e) {
e = (SolrException) e.getCause();
}
assertEquals(errCode, e.code());
} catch (SolrServerException ex) {
Throwable t = ex.getCause();
if (t instanceof SolrException) {
failed = true;
SolrException exception = (SolrException) t;
assertEquals(errCode, exception.code());
}
} catch (Exception e) {
log.error("ERROR", e);
}
assertTrue(failed);
}
void vadd(String id, long version, String... params) throws Exception {
UpdateRequest req = new UpdateRequest();
req.add(sdoc("id", id, vfield, version));
for (int i = 0; i < params.length; i += 2) {
req.setParam(params[i], params[i + 1]);
}
solrServer.request(req);
}
void vaddFail(String id, long version, int errCode, String... params) throws Exception {
boolean failed = false;
try {
vadd(id, version, params);
} catch (SolrException e) {
failed = true;
if (e.getCause() instanceof SolrException && e.getCause() != e) {
e = (SolrException) e.getCause();
}
assertEquals(errCode, e.code());
} catch (SolrServerException ex) {
Throwable t = ex.getCause();
if (t instanceof SolrException) {
failed = true;
SolrException exception = (SolrException) t;
assertEquals(errCode, exception.code());
}
} catch (Exception e) {
log.error("ERROR", e);
}
assertTrue(failed);
}
void doQuery(SolrClient ss, String expectedDocs, String... queryParams) throws Exception {
List<String> strs = StrUtils.splitSmart(expectedDocs, ",", true);
Map<String, Object> expectedIds = new HashMap<>();
for (int i = 0; i < strs.size(); i += 2) {
String id = strs.get(i);
String vS = strs.get(i + 1);
Long v = Long.valueOf(vS);
expectedIds.put(id, v);
}
QueryResponse rsp = ss.query(params(queryParams));
Map<String, Object> obtainedIds = new HashMap<>();
for (SolrDocument doc : rsp.getResults()) {
obtainedIds.put((String) doc.get("id"), doc.get(vfield));
}
assertEquals(expectedIds, obtainedIds);
}
void doRealTimeGet(String ids, String versions) throws Exception {
Map<String, Object> expectedIds = new HashMap<>();
List<String> strs = StrUtils.splitSmart(ids, ",", true);
List<String> verS = StrUtils.splitSmart(versions, ",", true);
for (int i = 0; i < strs.size(); i++) {
if (!verS.isEmpty()) {
expectedIds.put(strs.get(i), Long.valueOf(verS.get(i)));
}
}
QueryResponse rsp = solrServer.query(params("qt", "/get", "ids", ids));
Map<String, Object> obtainedIds = new HashMap<>();
for (SolrDocument doc : rsp.getResults()) {
obtainedIds.put((String) doc.get("id"), doc.get(vfield));
}
assertEquals(expectedIds, obtainedIds);
}
void doDeleteByQuery(String q, String... reqParams) throws Exception {
UpdateRequest req = new UpdateRequest();
req.deleteByQuery(q);
req.setParams(params(reqParams));
req.process(solrServer);
}
}

View File

@ -1,359 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cloud.cdcr;
import java.lang.invoke.MethodHandles;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.MiniSolrCloudCluster;
import org.apache.solr.common.SolrInputDocument;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.carrotsearch.randomizedtesting.annotations.Nightly;
@Nightly // test is too long for non nightly
public class CdcrWithNodesRestartsTest extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
MiniSolrCloudCluster target, source;
CloudSolrClient sourceSolrClient, targetSolrClient;
private static String SOURCE_COLLECTION = "cdcr-source";
private static String TARGET_COLLECTION = "cdcr-target";
private static String ALL_Q = "*:*";
@BeforeClass
public static void beforeClass() {
System.clearProperty("solr.httpclient.retries");
System.clearProperty("solr.retries.on.forward");
System.clearProperty("solr.retries.to.followers");
}
@Before
public void before() throws Exception {
target = new MiniSolrCloudCluster(2, createTempDir(TARGET_COLLECTION), buildJettyConfig("/solr"));
System.setProperty("cdcr.target.zkHost", target.getZkServer().getZkAddress());
source = new MiniSolrCloudCluster(2, createTempDir(SOURCE_COLLECTION), buildJettyConfig("/solr"));
}
@After
public void after() throws Exception {
if (null != target) {
target.shutdown();
target = null;
}
if (null != source) {
source.shutdown();
source = null;
}
}
@Test
public void testBufferOnNonLeader() throws Exception {
createCollections();
CdcrTestsUtil.cdcrDisableBuffer(sourceSolrClient);
CdcrTestsUtil.cdcrStart(sourceSolrClient);
Thread.sleep(2000);
// index 100 docs
for (int i = 0; i < 100; i++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + i);
CdcrTestsUtil.index(source, "cdcr-source", doc);
sourceSolrClient.commit();
}
Thread.sleep(2000);
// restart all the nodes at source cluster, one by one
CdcrTestsUtil.restartClusterNodes(source, SOURCE_COLLECTION);
//verify cdcr has replicated docs
QueryResponse response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 100, response.getResults().getNumFound());
assertEquals("target docs mismatch", 100, CdcrTestsUtil.waitForClusterToSync(100, targetSolrClient));
CdcrTestsUtil.assertShardInSync(SOURCE_COLLECTION, "shard1", sourceSolrClient);
CdcrTestsUtil.assertShardInSync(TARGET_COLLECTION, "shard1", targetSolrClient);
CdcrTestsUtil.cdcrStop(sourceSolrClient);
CdcrTestsUtil.cdcrStop(targetSolrClient);
deleteCollections();
}
@Test
public void testUpdateLogSynchronisation() throws Exception {
createCollections();
CdcrTestsUtil.cdcrStart(sourceSolrClient);
Thread.sleep(2000);
// index 100 docs
for (int i = 0; i < 100; i++) {
// will perform a commit for every document and will create one tlog file per commit
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + i);
CdcrTestsUtil.index(source, "cdcr-source", doc, true);
}
Thread.sleep(2000);
//verify cdcr has replicated docs
QueryResponse response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 100, response.getResults().getNumFound());
assertEquals("target docs mismatch", 100, CdcrTestsUtil.waitForClusterToSync(100, targetSolrClient));
// Get the number of tlog files on the replicas (should be equal to the number of documents indexed)
int nTlogs = CdcrTestsUtil.getNumberOfTlogFilesOnReplicas(source);
// Disable the buffer - ulog synch should start on non-leader nodes
CdcrTestsUtil.cdcrDisableBuffer(sourceSolrClient);
Thread.sleep(2000);
int cnt = 15; // timeout after 15 seconds
int n = 0;
while (cnt > 0) {
// Index a new document with a commit to trigger update log cleaning
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + random().nextLong());
CdcrTestsUtil.index(source, "cdcr-source", doc, true);
// Check the update logs on non-leader nodes, the number of tlog files should decrease
n = CdcrTestsUtil.getNumberOfTlogFilesOnReplicas(source);
if (n < nTlogs) {
cnt = Integer.MIN_VALUE;
break;
}
cnt--;
Thread.sleep(1000);
}
if (cnt == 0) {
throw new AssertionError("Timeout while trying to assert update logs @ source_collection, " + n + " " + nTlogs);
}
CdcrTestsUtil.cdcrStop(sourceSolrClient);
CdcrTestsUtil.cdcrStop(targetSolrClient);
deleteCollections();
}
@Test
public void testReplicationAfterRestart() throws Exception {
createCollections();
CdcrTestsUtil.cdcrStart(sourceSolrClient); // start CDCR
Thread.sleep(2000);
//index 100 docs
for (int i = 0; i < 100; i++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + i);
CdcrTestsUtil.index(source, "cdcr-source", doc);
sourceSolrClient.commit();
}
Thread.sleep(2000);
// verify cdcr has replicated docs
QueryResponse response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 100, response.getResults().getNumFound());
assertEquals("target docs mismatch", 100, CdcrTestsUtil.waitForClusterToSync(100, targetSolrClient));
CdcrTestsUtil.assertShardInSync("cdcr-source", "shard1", sourceSolrClient);
// restart all the source cluster nodes
CdcrTestsUtil.restartClusterNodes(source, "cdcr-source");
sourceSolrClient = source.getSolrClient();
sourceSolrClient.setDefaultCollection("cdcr-source");
// verify still the docs are there
response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 100, response.getResults().getNumFound());
assertEquals("target docs mismatch", 100, CdcrTestsUtil.waitForClusterToSync(100, targetSolrClient));
// index 100 more
for (int i = 100; i < 200; i++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + i);
CdcrTestsUtil.index(source, "cdcr-source", doc);
sourceSolrClient.commit();
}
Thread.sleep(2000);
// verify still the docs are there
response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 200, response.getResults().getNumFound());
assertEquals("target docs mismatch", 200, CdcrTestsUtil.waitForClusterToSync(200, targetSolrClient));
CdcrTestsUtil.cdcrStop(sourceSolrClient);
CdcrTestsUtil.cdcrStop(targetSolrClient);
deleteCollections();
}
@Test
// commented out on: 24-Dec-2018 @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 14-Oct-2018
public void testReplicationAfterLeaderChange() throws Exception {
createCollections();
CdcrTestsUtil.cdcrStart(sourceSolrClient);
Thread.sleep(2000);
// index 100 docs
for (int i = 0; i < 100; i++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + i);
CdcrTestsUtil.index(source, "cdcr-source", doc);
sourceSolrClient.commit();
}
Thread.sleep(2000);
// verify cdcr has replicated docs
QueryResponse response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 100, response.getResults().getNumFound());
assertEquals("target docs mismatch", 100, CdcrTestsUtil.waitForClusterToSync(100, targetSolrClient));
CdcrTestsUtil.assertShardInSync("cdcr-source", "shard1", sourceSolrClient);
// restart one of the source cluster nodes
CdcrTestsUtil.restartClusterNode(source, "cdcr-source", 0);
sourceSolrClient = source.getSolrClient();
sourceSolrClient.setDefaultCollection("cdcr-source");
// add `100 more docs, 200 until now
for (int i = 100; i < 200; i++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + i);
CdcrTestsUtil.index(source, "cdcr-source", doc);
sourceSolrClient.commit();
}
Thread.sleep(2000);
// verify cdcr has replicated docs
response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 200, response.getResults().getNumFound());
assertEquals("target docs mismatch", 200, CdcrTestsUtil.waitForClusterToSync(200, targetSolrClient));
// restart the other source cluster node
CdcrTestsUtil.restartClusterNode(source, "cdcr-source", 1);
sourceSolrClient = source.getSolrClient();
sourceSolrClient.setDefaultCollection("cdcr-source");
// add `100 more docs, 300 until now
for (int i = 200; i < 300; i++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + i);
CdcrTestsUtil.index(source, "cdcr-source", doc);
sourceSolrClient.commit();
}
Thread.sleep(2000);
// verify cdcr has replicated docs
response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 300, response.getResults().getNumFound());
assertEquals("target docs mismatch", 300, CdcrTestsUtil.waitForClusterToSync(300, targetSolrClient));
// add a replica to 'target' collection
CollectionAdminRequest.addReplicaToShard(TARGET_COLLECTION, "shard1").
setNode(CdcrTestsUtil.getNonLeaderNode(target, TARGET_COLLECTION)).process(targetSolrClient);
Thread.sleep(2000);
// restart one of the target nodes
CdcrTestsUtil.restartClusterNode(source, "cdcr-target", 0);
targetSolrClient = target.getSolrClient();
targetSolrClient.setDefaultCollection("cdcr-target");
// add `100 more docs, 400 until now
for (int i = 300; i < 400; i++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + i);
CdcrTestsUtil.index(source, "cdcr-source", doc);
sourceSolrClient.commit();
}
Thread.sleep(2000);
// verify cdcr has replicated docs
response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 400, response.getResults().getNumFound());
assertEquals("target docs mismatch", 400, CdcrTestsUtil.waitForClusterToSync(400, targetSolrClient));
// restart the other target cluster node
CdcrTestsUtil.restartClusterNode(source, "cdcr-target", 1);
targetSolrClient = target.getSolrClient();
targetSolrClient.setDefaultCollection("cdcr-target");
// add `100 more docs, 500 until now
for (int i = 400; i < 500; i++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", "doc_" + i);
CdcrTestsUtil.index(source, "cdcr-source", doc);
sourceSolrClient.commit();
}
Thread.sleep(2000);
// verify cdcr has replicated docs
response = sourceSolrClient.query(new SolrQuery(ALL_Q));
assertEquals("source docs mismatch", 500, response.getResults().getNumFound());
assertEquals("target docs mismatch", 500, CdcrTestsUtil.waitForClusterToSync(500, targetSolrClient));
CdcrTestsUtil.cdcrStop(sourceSolrClient);
CdcrTestsUtil.cdcrStop(targetSolrClient);
deleteCollections();
}
private void createSourceCollection() throws Exception {
source.uploadConfigSet(configset("cdcr-source"), "cdcr-source");
CollectionAdminRequest.createCollection("cdcr-source", "cdcr-source", 1, 2)
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(source.getSolrClient());
Thread.sleep(1000);
sourceSolrClient = source.getSolrClient();
sourceSolrClient.setDefaultCollection("cdcr-source");
}
private void createTargetCollection() throws Exception {
target.uploadConfigSet(configset("cdcr-target"), "cdcr-target");
CollectionAdminRequest.createCollection("cdcr-target", "cdcr-target", 1, 1)
.withProperty("solr.directoryFactory", "solr.StandardDirectoryFactory")
.process(target.getSolrClient());
Thread.sleep(1000);
targetSolrClient = target.getSolrClient();
targetSolrClient.setDefaultCollection("cdcr-target");
}
private void deleteSourceCollection() throws Exception {
source.deleteAllCollections();
}
private void deleteTargetcollection() throws Exception {
target.deleteAllCollections();
}
private void createCollections() throws Exception {
createTargetCollection();
createSourceCollection();
}
private void deleteCollections() throws Exception {
deleteSourceCollection();
deleteTargetcollection();
}
}

View File

@ -42,7 +42,6 @@ public class TestRealTimeGet extends TestRTGBase {
@BeforeClass
public static void beforeClass() throws Exception {
randomizeUpdateLogImpl();
initCore("solrconfig-tlog.xml","schema_latest.xml");
}

View File

@ -76,7 +76,6 @@ public class TestRecovery extends SolrTestCaseJ4 {
public void beforeTest() throws Exception {
savedFactory = System.getProperty("solr.DirectoryFactory");
System.setProperty("solr.directoryFactory", "org.apache.solr.core.MockFSDirectoryFactory");
randomizeUpdateLogImpl();
initCore("solrconfig-tlog.xml","schema15.xml");
// validate that the schema was not changed to an unexpected state

View File

@ -51,7 +51,6 @@ public class TestStressRecovery extends TestRTGBase {
@Before
public void beforeClass() throws Exception {
randomizeUpdateLogImpl();
initCore("solrconfig-tlog.xml","schema15.xml");
}

View File

@ -1,783 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.update;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.apache.lucene.util.LuceneTestCase.Nightly;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.util.TestInjection;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.apache.solr.common.util.Utils.fromJSONString;
@Nightly
public class CdcrUpdateLogTest extends SolrTestCaseJ4 {
private static int timeout = 60; // acquire timeout in seconds. change this to a huge number when debugging to prevent threads from advancing.
// TODO: fix this test to not require FSDirectory
static String savedFactory;
@BeforeClass
public static void beforeClass() throws Exception {
savedFactory = System.getProperty("solr.DirectoryFactory");
System.setProperty("solr.directoryFactory", "org.apache.solr.core.MockFSDirectoryFactory");
initCore("solrconfig-cdcrupdatelog.xml", "schema15.xml");
}
@AfterClass
public static void afterClass() {
if (savedFactory == null) {
System.clearProperty("solr.directoryFactory");
} else {
System.setProperty("solr.directoryFactory", savedFactory);
}
}
private void clearCore() throws IOException {
clearIndex();
assertU(commit());
UpdateLog ulog = h.getCore().getUpdateHandler().getUpdateLog();
File logDir = new File(h.getCore().getUpdateHandler().getUpdateLog().getLogDir());
h.close();
String[] files = ulog.getLogList(logDir);
for (String file : files) {
File toDelete = new File(logDir, file);
Files.delete(toDelete.toPath()); // Should we really error out here?
}
assertEquals(0, ulog.getLogList(logDir).length);
createCore();
}
private void deleteByQuery(String q) throws Exception {
deleteByQueryAndGetVersion(q, null);
}
private void addDocs(int nDocs, int start, LinkedList<Long> versions) throws Exception {
for (int i = 0; i < nDocs; i++) {
versions.addFirst(addAndGetVersion(sdoc("id", Integer.toString(start + i)), null));
}
}
private static Long getVer(SolrQueryRequest req) throws Exception {
@SuppressWarnings({"rawtypes"})
Map rsp = (Map) fromJSONString(JQ(req));
@SuppressWarnings({"rawtypes"})
Map doc = null;
if (rsp.containsKey("doc")) {
doc = (Map) rsp.get("doc");
} else if (rsp.containsKey("docs")) {
@SuppressWarnings({"rawtypes"})
List lst = (List) rsp.get("docs");
if (lst.size() > 0) {
doc = (Map) lst.get(0);
}
} else if (rsp.containsKey("response")) {
@SuppressWarnings({"rawtypes"})
Map responseMap = (Map) rsp.get("response");
@SuppressWarnings({"rawtypes"})
List lst = (List) responseMap.get("docs");
if (lst.size() > 0) {
doc = (Map) lst.get(0);
}
}
if (doc == null) return null;
return (Long) doc.get("_version_");
}
@Test
public void testLogReaderNext() throws Exception {
this.clearCore();
int start = 0;
UpdateLog ulog = h.getCore().getUpdateHandler().getUpdateLog();
CdcrUpdateLog.CdcrLogReader reader = ((CdcrUpdateLog) ulog).newLogReader(); // test reader on empty updates log
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(11, start, versions);
start += 11;
assertU(commit());
for (int i = 0; i < 10; i++) { // 10 adds
assertNotNull(reader.next());
}
Object o = reader.next();
assertNotNull(o);
@SuppressWarnings({"rawtypes"})
List entry = (List) o;
int opAndFlags = (Integer) entry.get(0);
assertEquals(UpdateLog.COMMIT, opAndFlags & UpdateLog.OPERATION_MASK);
for (int i = 0; i < 11; i++) { // 11 adds
assertNotNull(reader.next());
}
o = reader.next();
assertNotNull(o);
entry = (List) o;
opAndFlags = (Integer) entry.get(0);
assertEquals(UpdateLog.COMMIT, opAndFlags & UpdateLog.OPERATION_MASK);
assertNull(reader.next());
// add a new tlog after having exhausted the reader
addDocs(10, start, versions);
start += 10;
assertU(commit());
// the reader should pick up the new tlog
for (int i = 0; i < 11; i++) { // 10 adds + 1 commit
assertNotNull(reader.next());
}
assertNull(reader.next());
}
/**
* Check the seek method of the log reader.
*/
@Test
public void testLogReaderSeek() throws Exception {
this.clearCore();
int start = 0;
UpdateLog ulog = h.getCore().getUpdateHandler().getUpdateLog();
CdcrUpdateLog.CdcrLogReader reader1 = ((CdcrUpdateLog) ulog).newLogReader();
CdcrUpdateLog.CdcrLogReader reader2 = ((CdcrUpdateLog) ulog).newLogReader();
CdcrUpdateLog.CdcrLogReader reader3 = ((CdcrUpdateLog) ulog).newLogReader();
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(11, start, versions);
start += 11;
assertU(commit());
addDocs(10, start, versions);
start += 10;
assertU(commit());
// Test case where target version is equal to startVersion of tlog file
long targetVersion = getVer(req("q", "id:10"));
assertTrue(reader1.seek(targetVersion));
Object o = reader1.next();
assertNotNull(o);
@SuppressWarnings({"rawtypes"})
List entry = (List) o;
long version = (Long) entry.get(1);
assertEquals(targetVersion, version);
assertNotNull(reader1.next());
// test case where target version is superior to startVersion of tlog file
targetVersion = getVer(req("q", "id:26"));
assertTrue(reader2.seek(targetVersion));
o = reader2.next();
assertNotNull(o);
entry = (List) o;
version = (Long) entry.get(1);
assertEquals(targetVersion, version);
assertNotNull(reader2.next());
// test case where target version is inferior to startVersion of oldest tlog file
targetVersion = getVer(req("q", "id:0")) - 1;
assertFalse(reader3.seek(targetVersion));
}
/**
* Check that the log reader is able to read the new tlog
* and pick up new entries as they appear.
*/
@Test
public void testLogReaderNextOnNewTLog() throws Exception {
this.clearCore();
int start = 0;
UpdateLog ulog = h.getCore().getUpdateHandler().getUpdateLog();
CdcrUpdateLog.CdcrLogReader reader = ((CdcrUpdateLog) ulog).newLogReader();
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(11, start, versions);
start += 11;
for (int i = 0; i < 22; i++) { // 21 adds + 1 commit
assertNotNull(reader.next());
}
// we should have reach the end of the new tlog
assertNull(reader.next());
addDocs(5, start, versions);
start += 5;
// the reader should now pick up the new updates
for (int i = 0; i < 5; i++) { // 5 adds
assertNotNull(reader.next());
}
assertNull(reader.next());
}
@Test
public void testRemoveOldLogs() throws Exception {
this.clearCore();
UpdateLog ulog = h.getCore().getUpdateHandler().getUpdateLog();
File logDir = new File(h.getCore().getUpdateHandler().getUpdateLog().getLogDir());
int start = 0;
int maxReq = 50;
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
assertU(commit());
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
addDocs(10, start, versions);
start += 10;
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
assertU(commit());
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
assertEquals(2, ulog.getLogList(logDir).length);
// Get a cdcr log reader to initialise a log pointer
CdcrUpdateLog.CdcrLogReader reader = ((CdcrUpdateLog) ulog).newLogReader();
addDocs(105, start, versions);
start += 105;
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
assertU(commit());
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
// the previous two tlogs should not be removed
assertEquals(3, ulog.getLogList(logDir).length);
// move the pointer past the first tlog
for (int i = 0; i <= 11; i++) { // 10 adds + 1 commit
assertNotNull(reader.next());
}
addDocs(10, start, versions);
start += 10;
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
assertU(commit());
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
// the first tlog should be removed
assertEquals(3, ulog.getLogList(logDir).length);
h.close();
createCore();
ulog = h.getCore().getUpdateHandler().getUpdateLog();
addDocs(105, start, versions);
start += 105;
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
assertU(commit());
assertJQ(req("qt", "/get", "getVersions", "" + maxReq), "/versions==" + versions.subList(0, Math.min(maxReq, start)));
// previous tlogs should be gone now
assertEquals(1, ulog.getLogList(logDir).length);
}
/**
* Check that the removal of old logs is taking into consideration
* multiple log pointers. Check also that the removal takes into consideration the
* numRecordsToKeep limit, even if the log pointers are ahead.
*/
@Test
public void testRemoveOldLogsMultiplePointers() throws Exception {
this.clearCore();
UpdateLog ulog = h.getCore().getUpdateHandler().getUpdateLog();
File logDir = new File(h.getCore().getUpdateHandler().getUpdateLog().getLogDir());
CdcrUpdateLog.CdcrLogReader reader1 = ((CdcrUpdateLog) ulog).newLogReader();
CdcrUpdateLog.CdcrLogReader reader2 = ((CdcrUpdateLog) ulog).newLogReader();
int start = 0;
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(105, start, versions);
start += 105;
assertU(commit());
// the previous two tlogs should not be removed
assertEquals(3, ulog.getLogList(logDir).length);
// move the first pointer past the first tlog
for (int i = 0; i <= 11; i++) { // 10 adds + 1 commit
assertNotNull(reader1.next());
}
addDocs(10, start, versions);
start += 10;
assertU(commit());
// the first tlog should not be removed
assertEquals(4, ulog.getLogList(logDir).length);
// move the second pointer past the first tlog
for (int i = 0; i <= 11; i++) { // 10 adds + 1 commit
assertNotNull(reader2.next());
}
addDocs(10, start, versions);
start += 10;
assertU(commit());
// the first tlog should be removed
assertEquals(4, ulog.getLogList(logDir).length);
// exhaust the readers
while (reader1.next() != null) {
}
while (reader2.next() != null) {
}
// the readers should point to the new tlog
// now add enough documents to trigger the numRecordsToKeep limit
addDocs(80, start, versions);
start += 80;
assertU(commit());
// the update log should kept the last 3 tlogs, which sum up to 100 records
assertEquals(3, ulog.getLogList(logDir).length);
}
/**
* Check that the output stream of an uncapped tlog is correctly reopen
* and that the commit is written during recovery.
*/
@Test
public void testClosingOutputStreamAfterLogReplay() throws Exception {
this.clearCore();
try {
TestInjection.skipIndexWriterCommitOnClose = true;
final Semaphore logReplay = new Semaphore(0);
final Semaphore logReplayFinish = new Semaphore(0);
UpdateLog.testing_logReplayHook = () -> {
try {
assertTrue(logReplay.tryAcquire(timeout, TimeUnit.SECONDS));
} catch (Exception e) {
throw new RuntimeException(e);
}
};
UpdateLog.testing_logReplayFinishHook = () -> logReplayFinish.release();
Deque<Long> versions = new ArrayDeque<>();
versions.addFirst(addAndGetVersion(sdoc("id", "A11"), null));
versions.addFirst(addAndGetVersion(sdoc("id", "A12"), null));
versions.addFirst(addAndGetVersion(sdoc("id", "A13"), null));
assertJQ(req("q", "*:*"), "/response/numFound==0");
assertJQ(req("qt", "/get", "getVersions", "" + versions.size()), "/versions==" + versions);
h.close();
createCore();
// Solr should kick this off now
// h.getCore().getUpdateHandler().getUpdateLog().recoverFromLog();
// verify that previous close didn't do a commit
// recovery should be blocked by our hook
assertJQ(req("q", "*:*"), "/response/numFound==0");
// unblock recovery
logReplay.release(1000);
// wait until recovery has finished
assertTrue(logReplayFinish.tryAcquire(timeout, TimeUnit.SECONDS));
assertJQ(req("q", "*:*"), "/response/numFound==3");
// The transaction log should have written a commit and close its output stream
UpdateLog ulog = h.getCore().getUpdateHandler().getUpdateLog();
assertEquals(0, ulog.logs.peekLast().refcount.get());
assertNull(ulog.logs.peekLast().channel);
ulog.logs.peekLast().incref(); // reopen the output stream to check if its ends with a commit
assertTrue(ulog.logs.peekLast().endsWithCommit());
ulog.logs.peekLast().decref();
} finally {
TestInjection.skipIndexWriterCommitOnClose = false; // reset
UpdateLog.testing_logReplayHook = null;
UpdateLog.testing_logReplayFinishHook = null;
}
}
/**
* Check the buffering of the old tlogs
*/
@Test
public void testBuffering() throws Exception {
this.clearCore();
CdcrUpdateLog ulog = (CdcrUpdateLog) h.getCore().getUpdateHandler().getUpdateLog();
File logDir = new File(h.getCore().getUpdateHandler().getUpdateLog().getLogDir());
int start = 0;
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(105, start, versions);
start += 105;
assertU(commit());
// the first two tlogs should have been removed
assertEquals(1, ulog.getLogList(logDir).length);
// enable buffer
ulog.enableBuffer();
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(105, start, versions);
start += 105;
assertU(commit());
// no tlog should have been removed
assertEquals(4, ulog.getLogList(logDir).length);
// disable buffer
ulog.disableBuffer();
addDocs(10, start, versions);
start += 10;
assertU(commit());
// old tlogs should have been removed
assertEquals(2, ulog.getLogList(logDir).length);
}
@Test
public void testSubReader() throws Exception {
this.clearCore();
CdcrUpdateLog ulog = (CdcrUpdateLog) h.getCore().getUpdateHandler().getUpdateLog();
File logDir = new File(h.getCore().getUpdateHandler().getUpdateLog().getLogDir());
CdcrUpdateLog.CdcrLogReader reader = ulog.newLogReader();
int start = 0;
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(10, start, versions);
start += 10;
assertU(commit());
assertEquals(2, ulog.getLogList(logDir).length);
// start to read the first tlog
for (int i = 0; i < 10; i++) {
assertNotNull(reader.next());
}
// instantiate a sub reader, and finish to read the first tlog (commit operation), plus start to read the
// second tlog (first five adds)
CdcrUpdateLog.CdcrLogReader subReader = reader.getSubReader();
for (int i = 0; i < 6; i++) {
assertNotNull(subReader.next());
}
// Five adds + one commit
assertEquals(6, subReader.getNumberOfRemainingRecords());
// Generate a new tlog
addDocs(105, start, versions);
start += 105;
assertU(commit());
// Even if the subreader is past the first tlog, the first tlog should not have been removed
// since the parent reader is still pointing to it
assertEquals(3, ulog.getLogList(logDir).length);
// fast forward the parent reader with the subreader
reader.forwardSeek(subReader);
subReader.close();
// After fast forward, the parent reader should be position on the doc15
@SuppressWarnings({"rawtypes"})
List o = (List) reader.next();
assertNotNull(o);
assertTrue("Expected SolrInputDocument but got" + o.toString() ,o.get(3) instanceof SolrInputDocument);
assertEquals("15", ((SolrInputDocument) o.get(3)).getFieldValue("id"));
// Finish to read the second tlog, and start to read the third one
for (int i = 0; i < 6; i++) {
assertNotNull(reader.next());
}
assertEquals(105, reader.getNumberOfRemainingRecords());
// Generate a new tlog to activate tlog cleaning
addDocs(10, start, versions);
start += 10;
assertU(commit());
// If the parent reader was correctly fast forwarded, it should be on the third tlog, and the first two should
// have been removed.
assertEquals(2, ulog.getLogList(logDir).length);
}
/**
* Check that the reader is correctly reset to its last position
*/
@Test
public void testResetToLastPosition() throws Exception {
this.clearCore();
CdcrUpdateLog ulog = (CdcrUpdateLog) h.getCore().getUpdateHandler().getUpdateLog();
File logDir = new File(h.getCore().getUpdateHandler().getUpdateLog().getLogDir());
CdcrUpdateLog.CdcrLogReader reader = ulog.newLogReader();
int start = 0;
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(10, start, versions);
start += 10;
assertU(commit());
assertEquals(2, ulog.getLogList(logDir).length);
for (int i = 0; i < 22; i++) {
Object o = reader.next();
assertNotNull(o);
// reset to last position
reader.resetToLastPosition();
// we should read the same update operation, i.e., same version number
assertEquals(((List) o).get(1), ((List) reader.next()).get(1));
}
assertNull(reader.next());
}
/**
* Check that the reader is correctly reset to its last position
*/
@Test
public void testGetNumberOfRemainingRecords() throws Exception {
try {
TestInjection.skipIndexWriterCommitOnClose = true;
final Semaphore logReplayFinish = new Semaphore(0);
UpdateLog.testing_logReplayFinishHook = () -> logReplayFinish.release();
this.clearCore();
int start = 0;
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
assertU(commit());
addDocs(10, start, versions);
start += 10;
h.close();
logReplayFinish.drainPermits();
createCore();
// At this stage, we have re-opened a capped tlog, and an uncapped tlog.
// check that the number of remaining records is correctly computed in these two cases
CdcrUpdateLog ulog = (CdcrUpdateLog) h.getCore().getUpdateHandler().getUpdateLog();
CdcrUpdateLog.CdcrLogReader reader = ulog.newLogReader();
// wait for the replay to finish
assertTrue(logReplayFinish.tryAcquire(timeout, TimeUnit.SECONDS));
// 20 records + 2 commits
assertEquals(22, reader.getNumberOfRemainingRecords());
for (int i = 0; i < 22; i++) {
Object o = reader.next();
assertNotNull(o);
assertEquals(22 - (i + 1), reader.getNumberOfRemainingRecords());
}
assertNull(reader.next());
assertEquals(0, reader.getNumberOfRemainingRecords());
// It should pick up the new tlog files
addDocs(10, start, versions);
assertEquals(10, reader.getNumberOfRemainingRecords());
} finally {
TestInjection.skipIndexWriterCommitOnClose = false; // reset
UpdateLog.testing_logReplayFinishHook = null;
}
}
/**
* Check that the initialisation of the log reader is picking up the tlog file that is currently being
* written.
*/
@Test
public void testLogReaderInitOnNewTlog() throws Exception {
this.clearCore();
int start = 0;
// Start to index some documents to instantiate the new tlog
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
// Create the reader after the instantiation of the new tlog
UpdateLog ulog = h.getCore().getUpdateHandler().getUpdateLog();
CdcrUpdateLog.CdcrLogReader reader = ((CdcrUpdateLog) ulog).newLogReader();
// Continue to index documents and commits
addDocs(11, start, versions);
start += 11;
assertU(commit());
// check that the log reader was initialised with the new tlog
for (int i = 0; i < 22; i++) { // 21 adds + 1 commit
assertNotNull(reader.next());
}
// we should have reach the end of the new tlog
assertNull(reader.next());
}
/**
* Check that the absolute version number is used for the update log index and for the last entry read
*/
@Test
public void testAbsoluteLastVersion() throws Exception {
this.clearCore();
CdcrUpdateLog ulog = (CdcrUpdateLog) h.getCore().getUpdateHandler().getUpdateLog();
File logDir = new File(h.getCore().getUpdateHandler().getUpdateLog().getLogDir());
CdcrUpdateLog.CdcrLogReader reader = ulog.newLogReader();
int start = 0;
LinkedList<Long> versions = new LinkedList<>();
addDocs(10, start, versions);
start += 10;
deleteByQuery("*:*");
assertU(commit());
deleteByQuery("*:*");
addDocs(10, start, versions);
start += 10;
assertU(commit());
assertEquals(2, ulog.getLogList(logDir).length);
for (long version : ulog.getStartingVersions()) {
assertTrue(version > 0);
}
for (int i = 0; i < 10; i++) {
reader.next();
}
// first delete
Object o = reader.next();
assertTrue((Long) ((List) o).get(1) < 0);
assertTrue(reader.getLastVersion() > 0);
reader.next(); // commit
// second delete
o = reader.next();
assertTrue((Long) ((List) o).get(1) < 0);
assertTrue(reader.getLastVersion() > 0);
}
}

View File

@ -85,8 +85,6 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
// asserting inplace updates happen by checking the internal [docid]
systemSetPropertySolrTestsMergePolicyFactory(NoMergePolicyFactory.class.getName());
randomizeUpdateLogImpl();
initCore(configString, schemaString);
// sanity check that autocommits are disabled

View File

@ -390,8 +390,6 @@ Some _potential_ areas for improvement that _are not implemented yet_ are:
* Collections might be constrained by their size instead of or in addition to time or category value.
This might be implemented as another type of routed alias, or possibly as an option on the existing routed aliases
* Compatibility with CDCR.
* Option for deletion of aliases that also deletes the underlying collections in one step. Routed Aliases may quickly
create more collections than expected during initial testing. Removing them after such events is overly tedious.

View File

@ -1,321 +0,0 @@
= CDCR API
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF 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.
The CDCR API is used to control and monitor the replication process. Control actions are performed at a collection level, i.e., by using the following base URL for API calls: `\http://localhost:8983/solr/<collection>/cdcr`.
[WARNING]
.CDCR is deprecated
====
This feature (in its current form) is deprecated and will likely be removed in 9.0.
See <<cross-data-center-replication-cdcr.adoc#cross-data-center-replication-cdcr,Cross Data Center Replication>> for more details.
====
Monitor actions are performed at a core level, i.e., by using the following base URL for API calls: `\http://localhost:8983/solr/<core>/cdcr`.
Currently, none of the CDCR API calls have parameters.
== API Entry Points
*Control*
* `<collection>/cdcr?action=STATUS`: <<CDCR STATUS,Returns the current state>> of CDCR.
* `<collection>/cdcr?action=START`: <<CDCR START,Starts CDCR>> replication
* `<collection>/cdcr?action=STOP`: <<CDCR STOP,Stops CDCR>> replication.
* `<collection>/cdcr?action=ENABLEBUFFER`: <<ENABLEBUFFER,Enables the buffering>> of updates.
* `<collection>/cdcr?action=DISABLEBUFFER`: <<DISABLEBUFFER,Disables the buffering>> of updates.
*Monitoring*
* `core/cdcr?action=QUEUES`: <<QUEUES,Fetches statistics about the queue>> for each replica and about the update logs.
* `core/cdcr?action=OPS`: <<OPS,Fetches statistics about the replication performance>> (operations per second) for each replica.
* `core/cdcr?action=ERRORS`: <<ERRORS,Fetches statistics and other information about replication errors>> for each replica.
== Control Commands
=== CDCR STATUS
`solr/<collection>/cdcr?action=STATUS`
==== CDCR Status Example
*Input*
[source,text]
----
http://localhost:8983/solr/techproducts/cdcr?action=STATUS
----
*Output*
[source,json]
----
{
"responseHeader": {
"status": 0,
"QTime": 0
},
"status": {
"process": "stopped",
"buffer": "enabled"
}
}
----
=== ENABLEBUFFER
`solr/<collection>/cdcr?action=ENABLEBUFFER`
==== Enable Buffer Example
*Input*
[source,text]
----
http://localhost:8983/solr/techproducts/cdcr?action=ENABLEBUFFER
----
*Output*
[source,json]
----
{
"responseHeader": {
"status": 0,
"QTime": 0
},
"status": {
"process": "started",
"buffer": "enabled"
}
}
----
=== DISABLEBUFFER
`solr/<collection>/cdcr?action=DISABLEBUFFER`
==== Disable Buffer Example
*Input*
[source,text]
----
http://localhost:8983/solr/techproducts/cdcr?action=DISABLEBUFFER
----
*Output*
[source,json]
----
{
"responseHeader": {
"status": 0,
"QTime": 0
},
"status": {
"process": "started",
"buffer": "disabled"
}
}
----
=== CDCR START
`solr/<collection>/cdcr?action=START`
==== CDCR Start Examples
*Input*
[source,text]
----
http://localhost:8983/solr/techproducts/cdcr?action=START
----
*Output*
[source,json]
----
{
"responseHeader": {
"status": 0,
"QTime": 0
},
"status": {
"process": "started",
"buffer": "enabled"
}
}
----
=== CDCR STOP
`solr/<collection>/cdcr?action=STOP`
==== CDCR Stop Examples
*Input*
[source,text]
----
http://localhost:8983/solr/techproducts/cdcr?action=STOP
----
*Output*
[source,json]
----
{
"responseHeader": {
"status": 0,
"QTime": 0
},
"status": {
"process": "stopped",
"buffer": "enabled"
}
}
----
== CDCR Monitoring Commands
=== QUEUES
`solr/<core>/cdcr?action=QUEUES`
==== QUEUES Response
The output is composed of a list “queues” which contains a list of (ZooKeeper) Target hosts, themselves containing a list of Target collections. For each collection, the current size of the queue and the timestamp of the last update operation successfully processed is provided. The timestamp of the update operation is the original timestamp, i.e., the time this operation was processed on the Source SolrCloud. This allows an estimate the latency of the replication process.
The “queues” object also contains information about the update logs, such as the size (in bytes) of the update logs on disk (`tlogTotalSize`), the number of transaction log files (`tlogTotalCount`) and the status of the update logs synchronizer (`updateLogSynchronizer`).
==== QUEUES Examples
*Input*
[source,text]
----
http://localhost:8983/solr/<replica_name>/cdcr?action=QUEUES
----
*Output*
[source,json]
----
{
"responseHeader":{
"status": 0,
"QTime": 1
},
"queues":{
"127.0.0.1: 40342/solr":{
"Target_collection":{
"queueSize": 104,
"lastTimestamp": "2014-12-02T10:32:15.879Z"
}
}
},
"tlogTotalSize":3817,
"tlogTotalCount":1,
"updateLogSynchronizer": "stopped"
}
----
=== OPS
`solr/<core>/cdcr?action=OPS`
==== OPS Response
Provides the average number of operations as a sum and broken down by adds/deletes.
==== OPS Examples
*Input*
[source,text]
----
http://localhost:8983/solr/<replica_name>/cdcr?action=OPS
----
*Output*
[source,json]
----
{
"responseHeader":{
"status":0,
"QTime":1
},
"operationsPerSecond":{
"127.0.0.1: 59661/solr":{
"Target_collection":{
"all": 297.102944952749052,
"adds": 297.102944952749052,
"deletes": 0.0
}
}
}
}
----
=== ERRORS
`solr/<core>/cdcr?action=ERRORS`
==== ERRORS Response
Provides the number of consecutive errors encountered by the replicator thread, the number of bad requests or internal errors since the start of the replication process, and a list of the last errors encountered ordered by timestamp.
==== ERRORS Examples
*Input*
[source,text]
----
http://localhost:8983/solr/<replica_name>/cdcr?action=ERRORS
----
*Output*
[source,json]
----
{
"responseHeader":{
"status":0,
"QTime":2
},
"errors": {
"127.0.0.1: 36872/solr":{
"Target_collection":{
"consecutiveErrors":3,
"bad_request":0,
"internal":3,
"last":{
"2014-12-02T11:04:42.523Z":"internal",
"2014-12-02T11:04:39.223Z":"internal",
"2014-12-02T11:04:38.22Z":"internal"
}
}
}
}
}
----

View File

@ -1,167 +0,0 @@
= CDCR Architecture
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF 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.
== CDCR Architecture Overview
With CDCR, Source and Target data centers can each serve search queries when CDCR is operating. The Target data center will lag somewhat behind the Source cluster due to propagation delays.
[WARNING]
.CDCR is deprecated
====
This feature (in its current form) is deprecated and will likely be removed in 9.0.
See <<cross-data-center-replication-cdcr.adoc#cross-data-center-replication-cdcr,Cross Data Center Replication>> for more details.
====
Data changes on the Source data center are replicated to the Target data center only after they are persisted to disk. The data changes can be replicated in near real-time (with a small delay) or could be scheduled to be sent at longer intervals to the Target data center. CDCR can "bootstrap" the collection to the Target data center. Since this is a full copy of the entire index, network bandwidth should be considered. Of course both Source and Target collections may be empty to start.
Each shard leader in the Source data center will be responsible for replicating its updates to the corresponding leader in the Target data center. When receiving updates from the Source data center, shard leaders in the Target data center will replicate the changes to their own replicas as normal SolrCloud updates.
This replication model is designed to tolerate some degradation in connectivity, accommodate limited bandwidth, and support batch updates to optimize communication.
Replication supports both a new empty index and pre-built indexes. In the scenario where the replication is set up on a pre-built index in the Source cluster and nothing on the Target cluster, CDCR will replicate the _entire_ index from the Source to Target.
The directional nature of the implementation implies a "push" model from the Source collection to the Target collection. Therefore, the Source configuration must be able to "see" the ZooKeeper ensemble in the Target cluster. The ZooKeeper ensemble is provided configured in the Source's `solrconfig.xml` file.
CDCR is configured to replicate from collections in the Source cluster to collections in the Target cluster on a collection-by-collection basis. Since CDCR is configured in `solrconfig.xml` (on both Source and Target clusters), the settings can be tailored for the needs of each collection.
CDCR can be configured to replicate from one collection to a second collection _within the same cluster_. That is a specialized scenario not covered in this Guide.
=== Uni-Directional Architecture
When uni-directional updates are configured, updates and deletes are first written to the Source cluster, then forwarded to one or more Target data centers, as illustrated in this graphic:
.Uni-Directional Data Flow
image::images/cross-data-center-replication-cdcr-/CDCR_arch.png[image,width=700,height=525]
With uni-directional updates, the Target data center(s) will not propagate updates such as adds, updates, or deletes to the Source data center and updates should not be sent to any of the Target data center(s).
The data flow sequence is:
. A shard leader receives a new update that is processed by its update processor chain.
. The data update is first applied to the local index.
. Upon successful application of the data update on the local index, the data update is added to CDCR's Update Logs queue.
. After the data update is persisted to disk, the data update is sent to the replicas within the data center.
. After Step 4 is successful, CDCR reads the data update from the Update Logs and pushes it to the corresponding collection in the Target data center. This is necessary in order to ensure consistency between the Source and Target data centers.
. The leader on the Target data center writes the data locally and forwards it to all its followers.
Steps 1, 2, 3 and 4 are performed synchronously by SolrCloud; Step 5 is performed asynchronously by a background thread. Given that CDCR replication is performed asynchronously, it becomes possible to push batch updates in order to minimize network communication overhead. Also, if CDCR is unable to push the update at a given time, for example, due to a degradation in connectivity, it can retry later without any impact on the Source data center.
One implication of the architecture is that the leaders in the Source cluster must be able to "see" the leaders in the Target cluster. Since leaders may change in both Source and Target collections, all nodes in the Source cluster must be able to "see" all Solr nodes in the Target cluster. Firewalls, ACL rules, etc., must be configured to allow this.
This design works most robustly if both the Source and Target clusters have the same number of shards. There is no requirement that the shards in the Source and Target collection have the same number of replicas.
Having different numbers of shards on the Source and Target cluster is possible, but is also an "expert" configuration as that option imposes certain constraints and is not generally recommended. Most of the scenarios where having differing numbers of shards are contemplated are better accomplished by hosting multiple shards on each Solr instance.
=== Bi-Directional Architecture
When bi-directional updates are configured, either cluster can act as a Source or a Target, and that role can shift between the clusters, as illustrated in this graphic:
.Bi-Directional Data Flow
image::images/cross-data-center-replication-cdcr-/CDCR_bidir.png[image,width=700,height=525]
With bi-directional updates, indexing and querying must be done on a single cluster at a time to maintain consistency. The second cluster is used when the first cluster is down. Simplifying, one cluster can act as Source and other as Target but both roles, Source and Target, cannot be assigned to any single cluster at the same time. Failover is handled smoothly without any configuration changes. Updates sent from Source data center to Target is not propagated back to Source when bi-directional updates are configured.
The data flow sequence is similar from Step 1 to 6 above, with an additional step:
[start=7]
. When bi-directional updates are configured, the updates received from Source are flagged on Target and not forwarded further.
All the behavior(s) and constraint(s) explained in uni-directional data flow are applicable to the respective Source and Target clusters in this scenario.
== Major Components of CDCR
What follows is a discussion of the key features and components in CDCRs architecture:
=== CDCR Configuration
In order to configure CDCR, the Source data center requires the host address of the ZooKeeper cluster associated with the Target data center. The ZooKeeper host address is the only information needed by CDCR to instantiate the communication with the Target Solr cluster. The CDCR configuration section of `solrconfig.xml` file on the Source cluster will therefore contain a list of ZooKeeper hosts. The CDCR configuration section of `solrconfig.xml` might also contain secondary/optional configuration, such as the number of CDC Replicator threads, batch updates related settings, etc.
=== CDCR Initialization
CDCR supports incremental updates to either new or existing collections. CDCR may not be able to keep up with very high volume updates, especially if there are significant communications latencies due to a slow "pipe" between the data centers. Some scenarios:
* There is an initial bulk load of a corpus followed by lower volume incremental updates. In this case, one can do the initial bulk load and then enable CDCR. See the section <<cdcr-config.adoc#initial-startup,Initial Startup>> for more information.
* The index is being built up from scratch, without a significant initial bulk load. CDCR can be set up on empty collections and keep them synchronized from the start.
* The index is always being updated at a volume too high for CDCR to keep up. This is especially possible in situations where the connection between the Source and Target data centers is poor. This scenario is unsuitable for CDCR in its current form.
=== Inter-Data Center Communication
The CDCR REST API is the primary form of end-user communication for admin commands.
A SolrJ client is used internally for CDCR operations. The SolrJ client gets its configuration information from the `solrconfig.xml` file. Users of CDCR will not interact directly with the internal SolrJ implementation and will interact with CDCR exclusively through the REST API.
=== Updates Tracking & Pushing
CDCR replicates data updates from the Source to the Target data center by leveraging Update Logs. These logs will replace SolrCloud's transaction log.
A background thread regularly checks the Update Logs for new entries, and then forwards them to the Target data center. The thread therefore needs to keep a checkpoint in the form of a pointer to the last update successfully processed in the Update Logs. Upon acknowledgement from the Target data center that updates have been successfully processed, the Update Logs pointer is updated to reflect the current checkpoint.
This pointer must be synchronized across all the replicas. In the case where the leader goes down and a new leader is elected, the new leader will be able to resume replication from the last update by using this synchronized pointer. The strategy to synchronize such a pointer across replicas will be explained next.
If for some reason, the Target data center is offline or fails to process the updates, the thread will periodically try to contact the Target data center and push the updates while buffering updates on the Source cluster. One implication of this is that the Source Update Logs directory should be periodically monitored as the updates will continue to accumulate and will not be purged until the connection to the Target data center is restored.
=== Synchronization of Update Checkpoints
A reliable synchronization of the update checkpoints between the shard leader and shard replicas is critical to avoid introducing inconsistency between the Source and Target data centers. Another important requirement is that the synchronization must be performed with minimal network traffic to maximize scalability.
In order to achieve this, the strategy is to:
* Uniquely identify each update operation. This unique identifier will serve as pointer.
* Rely on two storages: an ephemeral storage on the Source shard leader, and a persistent storage on the Target cluster.
The shard leader in the Source cluster will be in charge of generating a unique identifier for each update operation, and will keep a copy of the identifier of the last processed updates in memory. The identifier will be sent to the Target cluster as part of the update request. On the Target data center side, the shard leader will receive the update request, store it along with the unique identifier in the Update Logs, and replicate it to the other shards.
SolrCloud already provides a unique identifier for each update operation, i.e., a “version” number. This version number is generated using a time-based lmport clock which is incremented for each update operation sent. This provides a “happened-before” ordering of the update operations that will be leveraged in (1) the initialization of the update checkpoint on the Source cluster, and in (2) the maintenance strategy of the Update Logs.
The persistent storage on the Target cluster is used only during the election of a new shard leader on the Source cluster. If a shard leader goes down on the Source cluster and a new leader is elected, the new leader will contact the Target cluster to retrieve the last update checkpoint and instantiate its ephemeral pointer. On such a request, the Target cluster will retrieve the latest identifier received across all the shards, and send it back to the Source cluster. To retrieve the latest identifier, every shard leader will look up the identifier of the first entry in its Update Logs and send it back to a coordinator. The coordinator will have to select the highest among them.
This strategy does not require any additional network traffic and ensures reliable pointer synchronization. Consistency is principally achieved by leveraging SolrCloud. The update workflow of SolrCloud ensures that every update is applied to the leader and also to any of the replicas. If the leader goes down, a new leader is elected. During the leader election, a synchronization is performed between the new leader and the other replicas. This ensures that the new leader has a consistent Update Logs with the previous leader. Having a consistent Update Logs means that:
* On the Source cluster, the update checkpoint can be reused by the new leader.
* On the Target cluster, the update checkpoint will be consistent between the previous and new leader. This ensures the correctness of the update checkpoint sent by a newly elected leader from the Target cluster.
=== Maintenance of Update Logs
The CDCR replication logic requires modification to the maintenance logic of Update Logs on the Source data center. Initially, the Update Logs acts as a fixed size queue, limited to 100 update entries by default. In CDCR, the Update Logs must act as a queue of variable size as they need to keep track of all the updates up through the last processed update by the Target data center. Entries in the Update Logs are removed only when all pointers (one pointer per Target data center) are after them.
If the communication with one of the Target data center is slow, the Update Logs on the Source data center can grow to a substantial size. In such a scenario, it is necessary for the Update Logs to be able to efficiently find a given update operation given its identifier. Given that its identifier is an incremental number, it is possible to implement an efficient search strategy. Each transaction log file contains as part of its filename the version number of the first element. This is used to quickly traverse all the transaction log files and find the transaction log file containing one specific version number.
=== Monitoring Operations
CDCR provides the following monitoring capabilities over the replication operations:
* Monitoring of the outgoing and incoming replications, with information such as the Source and Target nodes, their status, etc.
* Statistics about the replication, with information such as operations (add/delete) per second, number of documents in the queue, etc.
Information about the lifecycle and statistics will be provided on a per-shard basis by the CDC Replicator thread. The CDCR API can then aggregate this information an a collection level.
=== CDC Replicator
The CDC Replicator is a background thread that is responsible for replicating updates from a Source data center to one or more Target data centers. It is responsible for providing monitoring information on a per-shard basis. As there can be a large number of collections and shards in a cluster, we will use a fixed-size pool of CDC Replicator threads that will be shared across shards.
=== CDCR Limitations
The current design of CDCR has some limitations. CDCR will continue to evolve over time and many of these limitations will be addressed. Among them are:
* CDCR is unlikely to be satisfactory for bulk-load situations where the update rate is high, especially if the bandwidth between the Source and Target clusters is restricted. In this scenario, the initial bulk load should be performed, the Source and Target data centers synchronized and CDCR be utilized for incremental updates.
* CDCR works most robustly with the same number of shards in the Source and Target collection. The shards in the two collections may have different numbers of replicas.
* Running CDCR with the indexes on HDFS is not currently supported, see the https://issues.apache.org/jira/browse/SOLR-9861[Solr CDCR over HDFS] JIRA issue.
* Configuration files (`solrconfig.xml`, `managed-schema`, etc.) are not automatically synchronized between the Source and Target clusters. This means that when the Source schema or `solrconfig.xml` files are changed, those changes must be replicated manually to the Target cluster. This includes adding fields by the <<schema-api.adoc#schema-api,Schema API>> or <<managed-resources.adoc#managed-resources,Managed Resources>> as well as hand editing those files.
* CDCR doesn't support <<basic-authentication-plugin.adoc#basic-authentication-plugin,Basic Authentication features>> across clusters.
* CDCR does not yet support TLOG or PULL replica types.

View File

@ -1,376 +0,0 @@
= CDCR Configuration
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF 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.
The Source and Target configurations differ in the case of the data centers being in separate clusters. "Cluster" here means separate ZooKeeper ensembles controlling disjoint Solr instances. Whether these data centers are physically separated or not is immaterial for this discussion.
[WARNING]
.CDCR is deprecated
====
This feature (in its current form) is deprecated and will likely be removed in 9.0.
See <<cross-data-center-replication-cdcr.adoc#cross-data-center-replication-cdcr,Cross Data Center Replication>> for more details.
====
As described in the section <<cdcr-architecture.adoc#cdcr-architecture,CDCR Architecture>>, two approaches are supported: uni-directional updates and bi-directional updates.
All CDCR configuration is done in the `solrconfig.xml` file. Because this is a per-collection configuration file, all CDCR configuration is done for each collection.
== Uni-Directional Updates
=== Source Configuration
Here is a sample of a Source configuration file, a section in `solrconfig.xml`. The presence of the `<replica>` section causes CDCR to use this cluster as the Source and it should not be present in the Target collections. Details about each setting are after the two examples. The source example has buffering disabled, the default is enabled:
[source,xml]
----
<requestHandler name="/cdcr" class="solr.CdcrRequestHandler">
<lst name="replica">
<str name="zkHost">10.240.18.211:2181,10.240.18.212:2181</str>
<!--
If you have chrooted your Solr information at the target you must include the chroot, for example:
<str name="zkHost">10.240.18.211:2181,10.240.18.212:2181/solr</str>
-->
<str name="source">collection1</str>
<str name="target">collection1</str>
</lst>
<lst name="replicator">
<str name="threadPoolSize">8</str>
<str name="schedule">1000</str>
<str name="batchSize">128</str>
</lst>
<lst name="updateLogSynchronizer">
<str name="schedule">1000</str>
</lst>
</requestHandler>
<!-- Modify the <updateLog> section of your existing <updateHandler>
in your config as below -->
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
<!--Any parameters from the original <updateLog> section -->
</updateLog>
<!-- Other configuration options such as autoCommit should still be present -->
</updateHandler>
----
=== Target Configuration
Here is a typical Target configuration.
Target instance must configure an update processor chain that is specific to CDCR. The update processor chain must include the `CdcrUpdateProcessorFactory`. The task of this processor is to ensure that the version numbers attached to update requests coming from a CDCR Source SolrCloud are reused and not overwritten by the Target. A properly configured Target configuration looks similar to this:
[source,xml]
----
<requestHandler name="/cdcr" class="solr.CdcrRequestHandler">
<!-- recommended for Target clusters -->
<lst name="buffer">
<str name="defaultState">disabled</str>
</lst>
</requestHandler>
<requestHandler name="/update" class="solr.UpdateRequestHandler">
<lst name="defaults">
<str name="update.chain">cdcr-processor-chain</str>
</lst>
</requestHandler>
<updateRequestProcessorChain name="cdcr-processor-chain">
<processor class="solr.CdcrUpdateProcessorFactory"/>
<processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>
<!-- Modify the <updateLog> section of your existing <updateHandler> in your
config as below -->
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
<!--Any parameters from the original <updateLog> section -->
</updateLog>
<!-- Other configuration options such as autoCommit should still be present -->
</updateHandler>
----
== Bi-Directional Updates
The configurations in both Cluster 1 and 2 are identical with respective `zkHost` string specified in each cluster's `solrconfig.xml`.
TIP: Both Cluster 1 and Cluster 2 can act as Source and Target at any given point of time but a cluster cannot be both Source and Target at the same time.
=== Cluster 1 Configuration
Here is a sample of a Cluster 1 configuration file, a section in `solrconfig.xml`. Cluster 2 `zkhost` string is specified in a `CdcrRequestHandler` declaration:
[source,xml]
----
<requestHandler name="/update" class="solr.UpdateRequestHandler">
<lst name="defaults">
<str name="update.chain">cdcr-processor-chain</str>
</lst>
</requestHandler>
<updateRequestProcessorChain name="cdcr-processor-chain">
<processor class="solr.CdcrUpdateProcessorFactory"/>
<processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>
<requestHandler name="/cdcr" class="solr.CdcrRequestHandler">
<lst name="replica">
<str name="zkHost">10.240.19.241:2181,10.240.19.242:2181</str>
<!--
If you have chrooted your Solr information at the target you must include the chroot, for example:
<str name="zkHost">10.240.19.241:2181,10.240.19.242:2181/solr</str>
-->
<str name="source">collection1</str>
<str name="target">collection1</str>
</lst>
<lst name="replicator">
<str name="threadPoolSize">8</str>
<str name="schedule">1000</str>
<str name="batchSize">128</str>
</lst>
<lst name="updateLogSynchronizer">
<str name="schedule">1000</str>
</lst>
</requestHandler>
<!-- Modify the <updateLog> section of your existing <updateHandler>
in your config as below -->
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
<!--Any parameters from the original <updateLog> section -->
</updateLog>
</updateHandler>
----
=== Cluster 2 Configuration
The configuration of the 2nd cluster is identical to the configuration of Cluster 1, with the Cluster 1 `zkHost` string specified in `CdcrRequestHandler` definition:
[source,xml]
----
<requestHandler name="/update" class="solr.UpdateRequestHandler">
<lst name="defaults">
<str name="update.chain">cdcr-processor-chain</str>
</lst>
</requestHandler>
<updateRequestProcessorChain name="cdcr-processor-chain">
<processor class="solr.CdcrUpdateProcessorFactory"/>
<processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>
<requestHandler name="/cdcr" class="solr.CdcrRequestHandler">
<lst name="replica">
<str name="zkHost">10.250.18.211:2181,10.250.18.212:2181</str>
<!--
If you have chrooted your Solr information at the target you must include the chroot, for example:
<str name="zkHost">10.250.18.211:2181,10.250.18.212:2181/solr</str>
-->
<str name="source">collection1</str>
<str name="target">collection1</str>
</lst>
<lst name="replicator">
<str name="threadPoolSize">8</str>
<str name="schedule">1000</str>
<str name="batchSize">128</str>
</lst>
<lst name="updateLogSynchronizer">
<str name="schedule">1000</str>
</lst>
</requestHandler>
<!-- Modify the <updateLog> section of your existing <updateHandler>
in your config as below -->
<updateHandler class="solr.DirectUpdateHandler2">
<updateLog class="solr.CdcrUpdateLog">
<str name="dir">${solr.ulog.dir:}</str>
<!--Any parameters from the original <updateLog> section -->
</updateLog>
</updateHandler>
----
== CDCR Configuration Parameters
The configuration details, defaults and options are as follows:
=== The Replica Element
CDCR can be configured to forward update requests to one or more Target collections. A Target collection is defined with a “replica” list as follows:
`zkHost`::
The host address for ZooKeeper of the Target SolrCloud. Usually this is a comma-separated list of addresses to each node in the Target ZooKeeper ensemble. This parameter is required.
`Source`::
The name of the collection on the Source SolrCloud to be replicated. This parameter is required.
`Target`::
The name of the collection on the Target SolrCloud to which updates will be forwarded. This parameter is required.
=== The Replicator Element
The CDC Replicator is the component in charge of forwarding updates to the replicas. The replicator will monitor the update logs of the Source collection and will forward any new updates to the Target collection.
The replicator uses a fixed thread pool to forward updates to multiple replicas in parallel. If more than one replica is configured, one thread will forward a batch of updates from one replica at a time in a round-robin fashion. The replicator can be configured with a “replicator” list as follows:
`threadPoolSize`::
The number of threads to use for forwarding updates. One thread per replica is recommended. The default is `2`.
`schedule`::
The delay in milliseconds for the monitoring the update log(s). The default is `10`.
`batchSize`::
The number of updates to send in one batch. The optimal size depends on the size of the documents. Large batches of large documents can increase your memory usage significantly. The default is `128`.
=== The updateLogSynchronizer Element
Expert: Non-leader nodes need to synchronize their update logs with their leader node from time to time in order to clean deprecated transaction log files. By default, such a synchronization process is performed every minute. The schedule of the synchronization can be modified with a “updateLogSynchronizer” list as follows:
TIP: If the updateLogSynchronizer element is omitted from the Source cluster, transaction logs may accumulate on non-leaders.
`schedule`::
The delay in milliseconds for synchronizing the update logs. The default is `60000`.
=== The Buffer Element
When buffering updates, the update logs will store all the updates indefinitely. It is best to disable buffering on both the Source and Target clusters during normal operation as when buffering is enabled the Update Logs will grow without limit. Enbling buffering is intended for special maintenance periods. Buffering can be disabled at startup with a “buffer” list and the parameter “defaultState” as follows:
`defaultState`::
The state of the buffer at startup. The default is `enabled`.
[TIP]
.Buffering should be enabled only for maintenance windows
====
Buffering is designed to augment maintenance windows. The following points should be kept in mind:
* When buffering is enabled, the Update Logs will grow without limit; they will never be purged.
* During normal operation, the Update Logs will automatically accrue on the Source data center if the Target data center is unavailable; It is not necessary to enable buffering for CDCR to handle routine network disruptions.
** For this reason, monitoring disk usage on the Source data center is recommended as an additional check that the Target data center is receiving updates.
* For uni-directional updates, buffering should _not_ be enabled on the Target data center as Update Logs would accrue without limit.
* If buffering is enabled and then disabled, the Update Logs will be removed when their contents have been sent to the Target data center. This process may take some time and is triggered by additional updates the Source cluster.
** Update Log cleanup is not triggered until a new update is sent to the Source data center.
====
== Initial Startup
=== Uni-Directional Approach
This is a general approach for initializing CDCR in a production environment. It's based upon an approach taken by the initial working installation of CDCR and generously contributed to illustrate a "real world" scenario.
* CDCR is used to keep a remote disaster-recovery instance available for production backup.
* This example as 26 clouds with 200 million assets per cloud (15GB indexes). Total document count is over 4.8 billion.
** Source and Target clouds were synched in 2-3 hour maintenance windows to establish the base index for the Targets.
As usual, it is good to start small. Sync a single cloud and monitor for a period of time before doing the others. You may need to adjust your settings several times before finding the right balance.
* Before starting, stop or pause the indexers. This is best done during a small maintenance window.
* Stop the SolrCloud instances at the Source.
* Upload the modified `solrconfig.xml` to ZooKeeper on both Source and Target as appropriate, see the examples above.
* Sync the index directories from the Source collection to Target collection across to the corresponding shard nodes. `rsync` works well for this.
+
For example, if there are two shards on collection1 with 2 replicas for each shard, copy the corresponding index directories from:
+
[width="75%",cols="45,10,45"]
|===
|shard1replica1Source |to |shard1replica1Target
|shard1replica2Source |to |shard1replica2Target
|shard2replica1Source |to |shard2replica1Target
|shard2replica2Source |to |shard2replica2Target
|===
* Start ZooKeeper on the Target (DR).
* Start SolrCloud on the Target (DR).
* Start ZooKeeper on the Source.
* Start SolrCloud on the Source. As a general rule, the Target (DR) should be started before the Source.
* Activate CDCR on Source instance using the CDCR API:
+
[source,text]
http://host:port/solr/<collection_name>/cdcr?action=START
+
There is no need to run the `/cdcr?action=START` command on the Target.
* Disable the buffer on the Target and Source:
+
[source,text]
http://host:port/solr/collection_name/cdcr?action=DISABLEBUFFER
+
* Re-enable indexing.
=== Bi-Directional Approach
[TIP]
====
When using the bi-directional approach, it is highly recommended to enable CDCR on both cluster-collections before any indexing has taken place.
====
Based on the same example from uni-directional solution, let's walk through the necessary steps:
* Before you begin, stop or pause any indexing processes. This is best done during a small maintenance window.
* Stop the SolrCloud instances in both Cluster 1 and Cluster 2.
* Upload the modified `solrconfig.xml` to ZooKeeper on both Cluster 1 and Cluster 2 as appropriate, see the examples above in the section <<Bi-Directional Updates>>.
* If documents were indexed prior to this exercise, sync the index directories from the Cluster 1 collection to the Cluster 2 collection to the corresponding shard nodes or vice versa. The `rsync` utility works well for this if it's available on your server. Check to be sure the the updated index is copied across.
+
For example, if there are 2 shards on collection 'cluster1' (the updated collection) with 2 replicas for each shard, copy the corresponding index directories from:
+
[width="75%",cols="45,10,45"]
|===
|shard1replica1cluster1 |to |shard1replica1cluster2
|shard1replica2cluster1 |to |shard1replica2cluster2
|shard2replica1cluster1 |to |shard2replica1cluster2
|shard2replica2cluster1 |to |shard2replica2cluster2
|===
* Start ZooKeeper on Cluster 1.
* Start ZooKeeper on Cluster 2.
* Start SolrCloud on Cluster 1.
* Start SolrCloud on Cluster 2.
* If not present, create respective collections in both Cluster 1 and Cluster 2.
* Activate the CDCR on Cluster 1 and Cluster 2 instance using the CDCR API:
+
[source,text]
http://host:port/solr/<collection_name>/cdcr?action=START
+
* Disable the buffer on Cluster 1 and Cluster 2:
+
[source,text]
http://host:port/solr/collection_name/cdcr?action=DISABLEBUFFER
+
* Re-enable indexing.
== ZooKeeper Settings
With CDCR, the Target ZooKeepers will have connections from the Target clouds and the Source clouds. You may need to increase the `maxClientCnxns` setting in `zoo.cfg`.
[source,text]
----
## set numbers of connection to 800 from client
## is maxClientCnxns=0 that means no limit
maxClientCnxns=800
----

View File

@ -1,49 +0,0 @@
= Cross Data Center Replication Operations
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF 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.
[WARNING]
.CDCR is deprecated
====
This feature (in its current form) is deprecated and will likely be removed in 9.0.
See <<cross-data-center-replication-cdcr.adoc#cross-data-center-replication-cdcr,Cross Data Center Replication>> for more details.
====
== Monitoring
. Network and disk space monitoring are essential. Ensure that the system has plenty of available storage to queue up changes if there is a disconnect between the Source and Target. A network outage between the two data centers can cause your disk usage to grow. Some tips:
.. Set a monitor for your disks to send alerts when the disk gets over a certain percentage (e.g., 70%).
.. Run a test. With moderate indexing, how long can the system queue changes before you run out of disk space?
. Create a simple way to check the counts between the Source and the Target.
.. Keep in mind that if indexing is running, the Source and Target may not match document for document. Set an alert to fire if the difference is greater than some percentage of the overall cloud size.
== Upgrading and Patching Production
When rolling in upgrades to your indexer or application, you should shutdown the Source and the Target. Depending on your setup, you may want to pause/stop indexing, deploy the release or patch, then re-enable indexing. Then start the Target last.
* There is no need to reissue the DISABLEBUFFERS or START commands. These are persisted.
* After starting the Target, run a simple test. Add a test document to each of the Source clouds. Then check for it on the Target.
[source,bash]
----
#send to the Source
curl http://<Source>/solr/cloud1/update -H 'Content-type:application/json' -d '[{"SKU":"ABC"}]'
#check the Target
curl "http://<Target>:8983/solr/<collection_name>/select?q=SKU:ABC&indent=true"
----

View File

@ -1,63 +0,0 @@
= Cross Data Center Replication (CDCR)
:page-children: cdcr-architecture, cdcr-config, cdcr-operations, cdcr-api
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF 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.
Cross Data Center Replication (CDCR) allows you to create multiple SolrCloud data centers and keep them in sync.
[WARNING]
.CDCR is deprecated
====
This feature (in its current form) is deprecated and will likely be removed in 9.0.
Anyone currently using CDCR should consider migrating away from it.
There are several open issues which make CDCR complex to maintain and generally unstable.
The simplest alternative to CDCR is to manually maintain two Solr implementations in separate data centers and manage them as completely separate installations.
While this may require more initial setup, it will be more stable in the long run.
There are other alternatives, and the Solr community is working to identify the best recommended replacement in time for 9.0.
====
== What is CDCR?
The <<solrcloud.adoc#solrcloud,SolrCloud>> architecture is designed to support <<near-real-time-searching.adoc#near-real-time-searching,Near Real Time (NRT)>> searches on a Solr collection that usual consists of multiple nodes in a single data center. CDCR augments this model by forwarding updates from a Solr collection in one data center to a parallel Solr collection in another data center where the network latencies are greater than the SolrCloud model was designed to accommodate.
For more information about CDCR, see the following sections:
* *<<cdcr-architecture.adoc#cdcr-architecture,CDCR Architecture>>*: A detailed overview of how CDCR works.
* *<<cdcr-config.adoc#cdcr-config,CDCR Configuration>>*: How to set up and initialize CDCR for your cluster.
* *<<cdcr-operations.adoc#cdcr-operations,CDCR Operations>>*: Information on monitoring CDCR and upgrading your cluster when using CDCR.
* *<<cdcr-api.adoc#cdcr-api,CDCR API>>*: Reference for the CDCR API.
// Are there any terms here that are new? If not, I think we should remove this.
== CDCR Glossary
For the purposes of discussing CDCR, the following terminology is used. If you are already familiar with SolrCloud, many of these terms will already be familiar to you.
[glossary]
Node:: A JVM instance running Solr; a server.
Cluster:: A set of Solr nodes managed as a single unit by a ZooKeeper ensemble hosting one or more Collections.
Data Center:: A group of networked servers hosting a Solr cluster. For CDCR, the terms _Cluster_ and _Data Center_ are interchangeable as we assume that each Solr cluster is hosted in a different group of networked servers.
Shard:: A sub-index of a single logical collection. This may be spread across multiple nodes of the cluster. Each shard can have 1-N replicas.
Leader:: Each shard has replica identified as its leader. All the writes for documents belonging to a shard are routed through the leader.
Replica:: A copy of a shard for use in failover or load balancing. Replicas comprising a shard can either be leaders or non-leaders.
Follower:: A convenience term for a replica that is _not_ the leader of a shard.
Collection:: A logical index, consisting of one or more shards. A cluster can have multiple collections.
Update:: An operation that changes the collection's index in any way. This could be adding a new document, deleting documents or changing a document.
Update Log(s):: An append-only log of write operations maintained by each node.

View File

@ -44,7 +44,7 @@ Built on streaming expressions, new in Solr 6 is a <<parallel-sql-interface.adoc
=== Cross Data Center Replication
Replication across data centers is now possible with <<cross-data-center-replication-cdcr.adoc#cross-data-center-replication-cdcr,Cross Data Center Replication>>. Using an active-passive model, a SolrCloud cluster can be replicated to another data center, and monitored with a new API.
Replication across data centers is now possible with Cross Data Center Replication. Using an active-passive model, a SolrCloud cluster can be replicated to another data center, and monitored with a new API.
=== Graph QueryParser

View File

@ -1,5 +1,5 @@
= SolrCloud
:page-children: getting-started-with-solrcloud, how-solrcloud-works, solrcloud-resilience, solrcloud-configuration-and-parameters, rule-based-replica-placement, cross-data-center-replication-cdcr
:page-children: getting-started-with-solrcloud, how-solrcloud-works, solrcloud-resilience, solrcloud-configuration-and-parameters, rule-based-replica-placement
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
@ -43,5 +43,4 @@ In this section, we'll cover everything you need to know about using Solr in Sol
** <<command-line-utilities.adoc#command-line-utilities,Command Line Utilities>>
** <<solrcloud-with-legacy-configuration-files.adoc#solrcloud-with-legacy-configuration-files,SolrCloud with Legacy Configuration Files>>
** <<configsets-api.adoc#configsets-api,Configsets API>>
* <<rule-based-replica-placement.adoc#rule-based-replica-placement,Rule-based Replica Placement>>
* <<cross-data-center-replication-cdcr.adoc#cross-data-center-replication-cdcr,Cross Data Center Replication (CDCR)>>
* <<rule-based-replica-placement.adoc#rule-based-replica-placement,Rule-based Replica Placement>>

View File

@ -2983,20 +2983,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
public static final String UPDATELOG_SYSPROP = "solr.tests.ulog";
/**
* randomizes the updateLog between different update log implementations for better test coverage
*/
public static void randomizeUpdateLogImpl() {
if (random().nextBoolean()) {
System.setProperty(UPDATELOG_SYSPROP, "solr.CdcrUpdateLog");
} else {
System.setProperty(UPDATELOG_SYSPROP,"solr.UpdateLog");
}
if (log.isInfoEnabled()) {
log.info("updateLog impl={}", System.getProperty(UPDATELOG_SYSPROP));
}
}
/**
* Sets various sys props related to user specified or randomized choices regarding the types
* of numerics that should be used in tests.