001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.procedure;
019
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import org.apache.hadoop.fs.FileStatus;
023import org.apache.hadoop.fs.FileSystem;
024import org.apache.hadoop.fs.Path;
025import org.apache.hadoop.hbase.HConstants;
026import org.apache.hadoop.hbase.NamespaceDescriptor;
027import org.apache.hadoop.hbase.NamespaceNotFoundException;
028import org.apache.hadoop.hbase.constraint.ConstraintException;
029import org.apache.hadoop.hbase.master.MasterFileSystem;
030import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
031import org.apache.hadoop.hbase.util.CommonFSUtils;
032import org.apache.yetus.audience.InterfaceAudience;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
037import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
038import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.DeleteNamespaceState;
039
040/**
041 * The procedure to remove a namespace.
042 */
043@InterfaceAudience.Private
044public class DeleteNamespaceProcedure
045  extends AbstractStateMachineNamespaceProcedure<DeleteNamespaceState> {
046  private static final Logger LOG = LoggerFactory.getLogger(DeleteNamespaceProcedure.class);
047
048  private NamespaceDescriptor nsDescriptor;
049  private String namespaceName;
050
051  public DeleteNamespaceProcedure() {
052  }
053
054  public DeleteNamespaceProcedure(MasterProcedureEnv env, String namespaceName) {
055    this(env, namespaceName, null);
056  }
057
058  public DeleteNamespaceProcedure(MasterProcedureEnv env, String namespaceName,
059    final ProcedurePrepareLatch latch) {
060    super(env, latch);
061    this.namespaceName = namespaceName;
062  }
063
064  @Override
065  protected Flow executeFromState(MasterProcedureEnv env, DeleteNamespaceState state)
066    throws InterruptedException {
067    LOG.info(this.toString());
068    try {
069      switch (state) {
070        case DELETE_NAMESPACE_PREPARE:
071          boolean present = prepareDelete(env);
072          releaseSyncLatch();
073          if (!present) {
074            assert isFailed() : "Delete namespace should have an exception here";
075            return Flow.NO_MORE_STATE;
076          }
077          setNextState(DeleteNamespaceState.DELETE_NAMESPACE_DELETE_FROM_NS_TABLE);
078          break;
079        case DELETE_NAMESPACE_DELETE_FROM_NS_TABLE:
080          deleteNamespace(env, namespaceName);
081          setNextState(DeleteNamespaceState.DELETE_NAMESPACE_DELETE_DIRECTORIES);
082          break;
083        case DELETE_NAMESPACE_REMOVE_FROM_ZK:
084          // not used any more
085          setNextState(DeleteNamespaceState.DELETE_NAMESPACE_DELETE_DIRECTORIES);
086          break;
087        case DELETE_NAMESPACE_DELETE_DIRECTORIES:
088          deleteDirectory(env, namespaceName);
089          setNextState(DeleteNamespaceState.DELETE_NAMESPACE_REMOVE_NAMESPACE_QUOTA);
090          break;
091        case DELETE_NAMESPACE_REMOVE_NAMESPACE_QUOTA:
092          removeNamespaceQuota(env, namespaceName);
093          return Flow.NO_MORE_STATE;
094        default:
095          throw new UnsupportedOperationException(this + " unhandled state=" + state);
096      }
097    } catch (IOException e) {
098      if (isRollbackSupported(state)) {
099        setFailure("master-delete-namespace", e);
100      } else {
101        LOG.warn("Retriable error trying to delete namespace " + namespaceName + " (in state="
102          + state + ")", e);
103      }
104    }
105    return Flow.HAS_MORE_STATE;
106  }
107
108  @Override
109  protected void rollbackState(final MasterProcedureEnv env, final DeleteNamespaceState state)
110    throws IOException {
111    if (state == DeleteNamespaceState.DELETE_NAMESPACE_PREPARE) {
112      // nothing to rollback, pre is just table-state checks.
113      // We can fail if the table does not exist or is not disabled.
114      // TODO: coprocessor rollback semantic is still undefined.
115      releaseSyncLatch();
116      return;
117    }
118
119    // The procedure doesn't have a rollback. The execution will succeed, at some point.
120    throw new UnsupportedOperationException("unhandled state=" + state);
121  }
122
123  @Override
124  protected boolean isRollbackSupported(final DeleteNamespaceState state) {
125    switch (state) {
126      case DELETE_NAMESPACE_PREPARE:
127        return true;
128      default:
129        return false;
130    }
131  }
132
133  @Override
134  protected DeleteNamespaceState getState(final int stateId) {
135    return DeleteNamespaceState.forNumber(stateId);
136  }
137
138  @Override
139  protected int getStateId(final DeleteNamespaceState state) {
140    return state.getNumber();
141  }
142
143  @Override
144  protected DeleteNamespaceState getInitialState() {
145    return DeleteNamespaceState.DELETE_NAMESPACE_PREPARE;
146  }
147
148  @Override
149  protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException {
150    super.serializeStateData(serializer);
151
152    MasterProcedureProtos.DeleteNamespaceStateData.Builder deleteNamespaceMsg =
153      MasterProcedureProtos.DeleteNamespaceStateData.newBuilder().setNamespaceName(namespaceName);
154    if (this.nsDescriptor != null) {
155      deleteNamespaceMsg
156        .setNamespaceDescriptor(ProtobufUtil.toProtoNamespaceDescriptor(this.nsDescriptor));
157    }
158    serializer.serialize(deleteNamespaceMsg.build());
159  }
160
161  @Override
162  protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException {
163    super.deserializeStateData(serializer);
164
165    MasterProcedureProtos.DeleteNamespaceStateData deleteNamespaceMsg =
166      serializer.deserialize(MasterProcedureProtos.DeleteNamespaceStateData.class);
167    namespaceName = deleteNamespaceMsg.getNamespaceName();
168    if (deleteNamespaceMsg.hasNamespaceDescriptor()) {
169      nsDescriptor =
170        ProtobufUtil.toNamespaceDescriptor(deleteNamespaceMsg.getNamespaceDescriptor());
171    }
172  }
173
174  @Override
175  public TableOperationType getTableOperationType() {
176    return TableOperationType.EDIT;
177  }
178
179  @Override
180  protected String getNamespaceName() {
181    return namespaceName;
182  }
183
184  /**
185   * Action before any real action of deleting namespace.
186   * @param env MasterProcedureEnv
187   */
188  private boolean prepareDelete(final MasterProcedureEnv env) throws IOException {
189    if (getTableNamespaceManager(env).doesNamespaceExist(namespaceName) == false) {
190      setFailure("master-delete-namespace", new NamespaceNotFoundException(namespaceName));
191      return false;
192    }
193    if (NamespaceDescriptor.RESERVED_NAMESPACES.contains(namespaceName)) {
194      setFailure("master-delete-namespace",
195        new ConstraintException("Reserved namespace " + namespaceName + " cannot be removed."));
196      return false;
197    }
198
199    int tableCount = 0;
200    try {
201      tableCount = env.getMasterServices().listTableDescriptorsByNamespace(namespaceName).size();
202    } catch (FileNotFoundException fnfe) {
203      setFailure("master-delete-namespace", new NamespaceNotFoundException(namespaceName));
204      return false;
205    }
206    if (tableCount > 0) {
207      setFailure("master-delete-namespace",
208        new ConstraintException("Only empty namespaces can be removed. Namespace " + namespaceName
209          + " has " + tableCount + " tables"));
210      return false;
211    }
212
213    // This is used for rollback
214    nsDescriptor = getTableNamespaceManager(env).get(namespaceName);
215    return true;
216  }
217
218  /**
219   * delete the row from the ns family in meta table.
220   * @param env           MasterProcedureEnv
221   * @param namespaceName name of the namespace in string format
222   */
223  private static void deleteNamespace(MasterProcedureEnv env, String namespaceName)
224    throws IOException {
225    getTableNamespaceManager(env).deleteNamespace(namespaceName);
226  }
227
228  /**
229   * Delete the namespace directories from the file system
230   * @param env           MasterProcedureEnv
231   * @param namespaceName name of the namespace in string format
232   */
233  private static void deleteDirectory(MasterProcedureEnv env, String namespaceName)
234    throws IOException {
235    MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
236    FileSystem fs = mfs.getFileSystem();
237    Path p = CommonFSUtils.getNamespaceDir(mfs.getRootDir(), namespaceName);
238
239    try {
240      for (FileStatus status : fs.listStatus(p)) {
241        if (!HConstants.HBASE_NON_TABLE_DIRS.contains(status.getPath().getName())) {
242          throw new IOException("Namespace directory contains table dir: " + status.getPath());
243        }
244      }
245      if (!fs.delete(CommonFSUtils.getNamespaceDir(mfs.getRootDir(), namespaceName), true)) {
246        throw new IOException("Failed to remove namespace: " + namespaceName);
247      }
248    } catch (FileNotFoundException e) {
249      // File already deleted, continue
250      LOG.debug("deleteDirectory throws exception: " + e);
251    }
252  }
253
254  /**
255   * remove quota for the namespace
256   * @param env           MasterProcedureEnv
257   * @param namespaceName name of the namespace in string format
258   **/
259  private static void removeNamespaceQuota(final MasterProcedureEnv env, final String namespaceName)
260    throws IOException {
261    env.getMasterServices().getMasterQuotaManager().removeNamespaceQuota(namespaceName);
262  }
263}