All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.cloud.api.collections.DeleteCollectionCmd Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * 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 static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
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;

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.DistributedClusterStateUpdater;
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.core.snapshots.SolrSnapshotManager;
import org.apache.solr.handler.admin.ConfigSetsHandler;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DeleteCollectionCmd implements CollApiCmds.CollectionApiCommand {
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  private static final Set okayExceptions =
      Collections.singleton(NonExistentCoreException.class.getName());

  private final CollectionCommandContext ccc;

  public DeleteCollectionCmd(CollectionCommandContext ccc) {
    this.ccc = ccc;
  }

  @Override
  public void call(ClusterState state, ZkNodeProps message, NamedList results)
      throws Exception {
    Object o = message.get(MaintainRoutedAliasCmd.INVOKED_BY_ROUTED_ALIAS);
    if (o != null) {
      // this will ensure the collection is removed from the alias before it disappears.
      ((Runnable) o).run();
    }
    final String extCollection = message.getStr(NAME);
    ZkStateReader zkStateReader = ccc.getZkStateReader();

    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 aliasReferences = checkAliasReference(zkStateReader, extCollection, followAliases);

    Aliases aliases = zkStateReader.getAliases();

    String collection;
    if (followAliases) {
      collection = aliases.resolveSimpleAlias(extCollection);
    } else {
      collection = extCollection;
    }

    // verify the placement modifications caused by the deletion are allowed
    DocCollection coll = state.getCollectionOrNull(collection);
    if (coll != null) {
      Assign.AssignStrategy assignStrategy = Assign.createAssignStrategy(ccc.getCoreContainer());
      assignStrategy.verifyDeleteCollection(ccc.getSolrCloudManager(), coll);
    }

    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;
        }
      }
      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);

      String asyncId = message.getStr(ASYNC);

      ZkNodeProps internalMsg = message.plus(NAME, collection);

      List failedReplicas =
          CollectionHandlingUtils.collectionCmd(
              internalMsg, params, results, null, asyncId, okayExceptions, ccc, state);
      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);
      if (ccc.getDistributedClusterStateUpdater().isDistributedStateUpdate()) {
        ccc.getDistributedClusterStateUpdater()
            .doSingleStateUpdate(
                DistributedClusterStateUpdater.MutatingCommand.ClusterDeleteCollection,
                m,
                ccc.getSolrCloudManager(),
                ccc.getZkStateReader());
      } else {
        ccc.offerStateUpdate(m);
      }

      // wait for a while until we don't see the collection
      zkStateReader.waitForState(collection, 60, TimeUnit.SECONDS, Objects::isNull);

      // we can delete any remaining unique aliases
      if (!aliasReferences.isEmpty()) {
        ccc.getZkStateReader()
            .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 = coll.getConfigName();

      if (ConfigSetsHandler.isAutoGeneratedConfigSet(configSetName)) {
        boolean configSetIsUsedByOtherCollection = false;

        // make sure the configSet is not shared with other collections
        // Similar to what happens in: ConfigSetCmds::deleteConfigSet
        for (Map.Entry entry :
            zkStateReader.getClusterState().getCollectionsMap().entrySet()) {
          String otherConfigSetName = entry.getValue().getConfigName();
          if (configSetName.equals(otherConfigSetName)) {
            configSetIsUsedByOtherCollection = true;
            break;
          }
        }

        if (!configSetIsUsedByOtherCollection) {
          // delete the config set
          ccc.getCoreContainer().getConfigSetService().deleteConfig(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 = DocCollection.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) {
        log.error("Cleaning up collection in zk was interrupted: {}", collection, e);
        Thread.currentThread().interrupt();
      } catch (KeeperException e) {
        log.error("Problem cleaning up collection in zk: {}", collection, e);
      }
    }
  }

  // This method returns the single collection aliases to delete, if present, or null
  private List checkAliasReference(
      ZkStateReader zkStateReader, String extCollection, boolean followAliases) throws Exception {
    Aliases aliases = zkStateReader.getAliases();
    List aliasesRefs = referencedByAlias(extCollection, aliases, followAliases);
    List 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 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());
  }
}