blob: c7aeb90874f66c29d28b770744e3f65b4bcbd262 [file] [log] [blame]
/*
* 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.api.collections;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.solr.cloud.Overseer;
import org.apache.solr.common.NonExistentCoreException;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.Aliases;
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.SolrZkClient;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CoreAdminParams;
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.common.util.Utils;
import org.apache.solr.core.SolrInfoBean;
import org.apache.solr.core.snapshots.SolrSnapshotManager;
import org.apache.solr.handler.admin.ConfigSetsHandler;
import org.apache.solr.handler.admin.MetricsHistoryHandler;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.common.params.CollectionAdminParams.COLOCATED_WITH;
import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
import static org.apache.solr.common.params.CollectionAdminParams.WITH_COLLECTION;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETE;
import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
import static org.apache.solr.common.params.CommonParams.NAME;
public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Set<String> okayExceptions = Collections.singleton(NonExistentCoreException.class.getName());
private final OverseerCollectionMessageHandler ocmh;
private final TimeSource timeSource;
public DeleteCollectionCmd(OverseerCollectionMessageHandler ocmh) {
this.ocmh = ocmh;
this.timeSource = ocmh.cloudManager.getTimeSource();
}
@Override
public void call(ClusterState state, ZkNodeProps message, @SuppressWarnings({"rawtypes"})NamedList results) throws Exception {
Object o = message.get(MaintainRoutedAliasCmd.INVOKED_BY_ROUTED_ALIAS);
if (o != null) {
((Runnable)o).run(); // this will ensure the collection is removed from the alias before it disappears.
}
final String extCollection = message.getStr(NAME);
ZkStateReader zkStateReader = ocmh.zkStateReader;
if (zkStateReader.aliasesManager != null) { // not a mock ZkStateReader
zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK
}
boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
List<String> aliasReferences = checkAliasReference(zkStateReader, extCollection, followAliases);
Aliases aliases = zkStateReader.getAliases();
String collection;
if (followAliases) {
collection = aliases.resolveSimpleAlias(extCollection);
} else {
collection = extCollection;
}
checkNotColocatedWith(zkStateReader, collection);
final boolean deleteHistory = message.getBool(CoreAdminParams.DELETE_METRICS_HISTORY, true);
boolean removeCounterNode = true;
try {
// Remove the snapshots meta-data for this collection in ZK. Deleting actual index files
// should be taken care of as part of collection delete operation.
SolrZkClient zkClient = zkStateReader.getZkClient();
SolrSnapshotManager.cleanupCollectionLevelSnapshots(zkClient, collection);
if (zkStateReader.getClusterState().getCollectionOrNull(collection) == null) {
if (zkStateReader.getZkClient().exists(ZkStateReader.COLLECTIONS_ZKNODE + "/" + collection, true)) {
// if the collection is not in the clusterstate, but is listed in zk, do nothing, it will just
// be removed in the finally - we cannot continue, because the below code will error if the collection
// is not in the clusterstate
return;
}
}
// remove collection-level metrics history
if (deleteHistory) {
MetricsHistoryHandler historyHandler = ocmh.overseer.getCoreContainer().getMetricsHistoryHandler();
if (historyHandler != null) {
String registry = SolrMetricManager.getRegistryName(SolrInfoBean.Group.collection, collection);
historyHandler.removeHistory(registry);
}
}
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(CoreAdminParams.ACTION, CoreAdminParams.CoreAdminAction.UNLOAD.toString());
params.set(CoreAdminParams.DELETE_INSTANCE_DIR, true);
params.set(CoreAdminParams.DELETE_DATA_DIR, true);
params.set(CoreAdminParams.DELETE_METRICS_HISTORY, deleteHistory);
String asyncId = message.getStr(ASYNC);
ZkNodeProps internalMsg = message.plus(NAME, collection);
@SuppressWarnings({"unchecked"})
List<Replica> failedReplicas = ocmh.collectionCmd(internalMsg, params, results, null, asyncId, okayExceptions);
for (Replica failedReplica : failedReplicas) {
boolean isSharedFS = failedReplica.getBool(ZkStateReader.SHARED_STORAGE_PROP, false) && failedReplica.get("dataDir") != null;
if (isSharedFS) {
// if the replica use a shared FS and it did not receive the unload message, then counter node should not be removed
// because when a new collection with same name is created, new replicas may reuse the old dataDir
removeCounterNode = false;
break;
}
}
ZkNodeProps m = new ZkNodeProps(Overseer.QUEUE_OPERATION, DELETE.toLower(), NAME, collection);
ocmh.overseer.offerStateUpdate(Utils.toJSON(m));
// wait for a while until we don't see the collection
zkStateReader.waitForState(collection, 60, TimeUnit.SECONDS, (collectionState) -> collectionState == null);
// we can delete any remaining unique aliases
if (!aliasReferences.isEmpty()) {
ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> {
for (String alias : aliasReferences) {
a = a.cloneWithCollectionAlias(alias, null);
}
return a;
});
}
// delete related config set iff: it is auto generated AND not related to any other collection
String configSetName = zkStateReader.readConfigName(collection);
if (ConfigSetsHandler.isAutoGeneratedConfigSet(configSetName)) {
boolean configSetIsUsedByOtherCollection = false;
// make sure the configSet is not shared with other collections
// Similar to what happens in: OverseerConfigSetMessageHandler::deleteConfigSet
for (Map.Entry<String, DocCollection> entry : zkStateReader.getClusterState().getCollectionsMap().entrySet()) {
String otherConfigSetName = null;
try {
otherConfigSetName = zkStateReader.readConfigName(entry.getKey());
} catch (KeeperException ex) {
// ignore 'no config found' errors
}
if (configSetName.equals(otherConfigSetName)) {
configSetIsUsedByOtherCollection = true;
break;
}
}
if (!configSetIsUsedByOtherCollection) {
// delete the config set
zkStateReader.getConfigManager().deleteConfigDir(configSetName);
}
}
// TimeOut timeout = new TimeOut(60, TimeUnit.SECONDS, timeSource);
// boolean removed = false;
// while (! timeout.hasTimedOut()) {
// timeout.sleep(100);
// removed = !zkStateReader.getClusterState().hasCollection(collection);
// if (removed) {
// timeout.sleep(500); // just a bit of time so it's more likely other
// // readers see on return
// break;
// }
// }
// if (!removed) {
// throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
// "Could not fully remove collection: " + collection);
// }
} finally {
try {
String collectionPath = ZkStateReader.getCollectionPathRoot(collection);
if (zkStateReader.getZkClient().exists(collectionPath, true)) {
if (removeCounterNode) {
zkStateReader.getZkClient().clean(collectionPath);
} else {
final String counterNodePath = Assign.getCounterNodePath(collection);
zkStateReader.getZkClient().clean(collectionPath, s -> !s.equals(counterNodePath));
}
}
} catch (InterruptedException e) {
SolrException.log(log, "Cleaning up collection in zk was interrupted:"
+ collection, e);
Thread.currentThread().interrupt();
} catch (KeeperException e) {
SolrException.log(log, "Problem cleaning up collection in zk:"
+ collection, e);
}
}
}
// This method returns the single collection aliases to delete, if present, or null
private List<String> checkAliasReference(ZkStateReader zkStateReader, String extCollection, boolean followAliases) throws Exception {
Aliases aliases = zkStateReader.getAliases();
List<String> aliasesRefs = referencedByAlias(extCollection, aliases, followAliases);
List<String> aliasesToDelete = new ArrayList<>();
if (aliasesRefs.size() > 0) {
zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK
aliases = zkStateReader.getAliases();
aliasesRefs = referencedByAlias(extCollection, aliases, followAliases);
String collection = followAliases ? aliases.resolveSimpleAlias(extCollection) : extCollection;
if (aliasesRefs.size() > 0) {
for (String alias : aliasesRefs) {
// for back-compat in 8.x we don't automatically remove other
// aliases that point only to this collection
if (!extCollection.equals(alias)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Collection : " + collection + " is part of aliases: " + aliasesRefs + ", remove or modify the aliases before removing this collection.");
} else {
aliasesToDelete.add(alias);
}
}
}
}
return aliasesToDelete;
}
public static List<String> referencedByAlias(String extCollection, Aliases aliases, boolean followAliases) throws IllegalArgumentException {
Objects.requireNonNull(aliases);
// this quickly produces error if the name is a complex alias
String collection = followAliases ? aliases.resolveSimpleAlias(extCollection) : extCollection;
return aliases.getCollectionAliasListMap().entrySet().stream()
.filter(e -> !e.getKey().equals(collection))
.filter(e -> e.getValue().contains(collection) || e.getValue().contains(extCollection))
.map(Map.Entry::getKey) // alias name
.collect(Collectors.toList());
}
private void checkNotColocatedWith(ZkStateReader zkStateReader, String collection) throws Exception {
DocCollection docCollection = zkStateReader.getClusterState().getCollectionOrNull(collection);
if (docCollection != null) {
String colocatedWith = docCollection.getStr(COLOCATED_WITH);
if (colocatedWith != null) {
DocCollection colocatedCollection = zkStateReader.getClusterState().getCollectionOrNull(colocatedWith);
if (colocatedCollection != null && collection.equals(colocatedCollection.getStr(WITH_COLLECTION))) {
// todo how do we clean up if reverse-link is not present?
// can't delete this collection because it is still co-located with another collection
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Collection: " + collection + " is co-located with collection: " + colocatedWith
+ " remove the link using modify collection API or delete the co-located collection: " + colocatedWith);
}
}
}
}
}