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;
019
020import java.io.IOException;
021import java.io.InterruptedIOException;
022import java.util.NavigableSet;
023import org.apache.commons.lang3.StringUtils;
024import org.apache.hadoop.conf.Configuration;
025import org.apache.hadoop.hbase.Cell;
026import org.apache.hadoop.hbase.CellBuilderFactory;
027import org.apache.hadoop.hbase.CellBuilderType;
028import org.apache.hadoop.hbase.CellUtil;
029import org.apache.hadoop.hbase.DoNotRetryIOException;
030import org.apache.hadoop.hbase.HTableDescriptor;
031import org.apache.hadoop.hbase.NamespaceDescriptor;
032import org.apache.hadoop.hbase.Stoppable;
033import org.apache.hadoop.hbase.TableName;
034import org.apache.hadoop.hbase.ZKNamespaceManager;
035import org.apache.hadoop.hbase.client.Delete;
036import org.apache.hadoop.hbase.client.Get;
037import org.apache.hadoop.hbase.client.Put;
038import org.apache.hadoop.hbase.client.Result;
039import org.apache.hadoop.hbase.client.ResultScanner;
040import org.apache.hadoop.hbase.client.Table;
041import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
042import org.apache.hadoop.hbase.client.TableState;
043import org.apache.hadoop.hbase.constraint.ConstraintException;
044import org.apache.hadoop.hbase.exceptions.TimeoutIOException;
045import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
046import org.apache.hadoop.hbase.master.procedure.ProcedurePrepareLatch;
047import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
048import org.apache.hadoop.hbase.util.Bytes;
049import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
050import org.apache.hadoop.hbase.util.Threads;
051import org.apache.yetus.audience.InterfaceAudience;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
056
057import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
058import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
059
060/**
061 * This is a helper class used internally to manage the namespace metadata that is stored in
062 * TableName.NAMESPACE_TABLE_NAME. It also mirrors updates to the ZK store by forwarding updates to
063 * {@link org.apache.hadoop.hbase.ZKNamespaceManager}. WARNING: Do not use. Go via the higher-level
064 * {@link ClusterSchema} API instead. This manager is likely to go aways anyways.
065 */
066@InterfaceAudience.Private
067@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "IS2_INCONSISTENT_SYNC",
068    justification = "TODO: synchronize access on nsTable but it is done in tiers above and this "
069      + "class is going away/shrinking")
070public class TableNamespaceManager implements Stoppable {
071  private static final Logger LOG = LoggerFactory.getLogger(TableNamespaceManager.class);
072  private volatile boolean stopped = false;
073
074  private Configuration conf;
075  private MasterServices masterServices;
076  private Table nsTable = null; // FindBugs: IS2_INCONSISTENT_SYNC TODO: Access is not synchronized
077  private ZKNamespaceManager zkNamespaceManager;
078  private boolean initialized;
079
080  public static final String KEY_MAX_REGIONS = "hbase.namespace.quota.maxregions";
081  public static final String KEY_MAX_TABLES = "hbase.namespace.quota.maxtables";
082  static final String NS_INIT_TIMEOUT = "hbase.master.namespace.init.timeout";
083  static final int DEFAULT_NS_INIT_TIMEOUT = 300000;
084
085  TableNamespaceManager(MasterServices masterServices) {
086    this.masterServices = masterServices;
087    this.conf = masterServices.getConfiguration();
088  }
089
090  public void start() throws IOException {
091    if (!masterServices.getTableDescriptors().exists(TableName.NAMESPACE_TABLE_NAME)) {
092      LOG.info("Namespace table not found. Creating...");
093      createNamespaceTable(masterServices);
094    }
095
096    try {
097      // Wait for the namespace table to be initialized.
098      long startTime = EnvironmentEdgeManager.currentTime();
099      int timeout = conf.getInt(NS_INIT_TIMEOUT, DEFAULT_NS_INIT_TIMEOUT);
100      while (!isTableAvailableAndInitialized()) {
101        if (EnvironmentEdgeManager.currentTime() - startTime + 100 > timeout) {
102          // We can't do anything if ns is not online.
103          throw new IOException("Timedout " + timeout + "ms waiting for namespace table to "
104            + "be assigned and enabled: " + getTableState());
105        }
106        Thread.sleep(100);
107      }
108    } catch (InterruptedException e) {
109      throw (InterruptedIOException) new InterruptedIOException().initCause(e);
110    }
111  }
112
113  private synchronized Table getNamespaceTable() throws IOException {
114    if (!isTableNamespaceManagerInitialized()) {
115      throw new IOException(this.getClass().getName() + " isn't ready to serve");
116    }
117    return nsTable;
118  }
119
120  /*
121   * check whether a namespace has already existed.
122   */
123  public boolean doesNamespaceExist(final String namespaceName) throws IOException {
124    if (nsTable == null) {
125      throw new IOException(this.getClass().getName() + " isn't ready to serve");
126    }
127    return (get(nsTable, namespaceName) != null);
128  }
129
130  public synchronized NamespaceDescriptor get(String name) throws IOException {
131    if (!isTableNamespaceManagerInitialized()) {
132      return null;
133    }
134    return zkNamespaceManager.get(name);
135  }
136
137  private NamespaceDescriptor get(Table table, String name) throws IOException {
138    Result res = table.get(new Get(Bytes.toBytes(name)));
139    if (res.isEmpty()) {
140      return null;
141    }
142    byte[] val =
143      CellUtil.cloneValue(res.getColumnLatestCell(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES,
144        HTableDescriptor.NAMESPACE_COL_DESC_BYTES));
145    return ProtobufUtil.toNamespaceDescriptor(HBaseProtos.NamespaceDescriptor.parseFrom(val));
146  }
147
148  public void insertIntoNSTable(final NamespaceDescriptor ns) throws IOException {
149    if (nsTable == null) {
150      throw new IOException(this.getClass().getName() + " isn't ready to serve");
151    }
152    byte[] row = Bytes.toBytes(ns.getName());
153    Put p = new Put(row, true);
154    p.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY).setRow(row)
155      .setFamily(TableDescriptorBuilder.NAMESPACE_FAMILY_INFO_BYTES)
156      .setQualifier(TableDescriptorBuilder.NAMESPACE_COL_DESC_BYTES).setTimestamp(p.getTimestamp())
157      .setType(Cell.Type.Put).setValue(ProtobufUtil.toProtoNamespaceDescriptor(ns).toByteArray())
158      .build());
159    nsTable.put(p);
160  }
161
162  public void updateZKNamespaceManager(final NamespaceDescriptor ns) throws IOException {
163    try {
164      zkNamespaceManager.update(ns);
165    } catch (IOException ex) {
166      String msg = "Failed to update namespace information in ZK.";
167      LOG.error(msg, ex);
168      throw new IOException(msg, ex);
169    }
170  }
171
172  public void removeFromNSTable(final String namespaceName) throws IOException {
173    if (nsTable == null) {
174      throw new IOException(this.getClass().getName() + " isn't ready to serve");
175    }
176    Delete d = new Delete(Bytes.toBytes(namespaceName));
177    nsTable.delete(d);
178  }
179
180  public void removeFromZKNamespaceManager(final String namespaceName) throws IOException {
181    zkNamespaceManager.remove(namespaceName);
182  }
183
184  public synchronized NavigableSet<NamespaceDescriptor> list() throws IOException {
185    NavigableSet<NamespaceDescriptor> ret =
186      Sets.newTreeSet(NamespaceDescriptor.NAMESPACE_DESCRIPTOR_COMPARATOR);
187    ResultScanner scanner =
188      getNamespaceTable().getScanner(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES);
189    try {
190      for (Result r : scanner) {
191        byte[] val =
192          CellUtil.cloneValue(r.getColumnLatestCell(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES,
193            HTableDescriptor.NAMESPACE_COL_DESC_BYTES));
194        ret.add(ProtobufUtil.toNamespaceDescriptor(HBaseProtos.NamespaceDescriptor.parseFrom(val)));
195      }
196    } finally {
197      scanner.close();
198    }
199    return ret;
200  }
201
202  private void createNamespaceTable(MasterServices masterServices) throws IOException {
203    masterServices.createSystemTable(HTableDescriptor.NAMESPACE_TABLEDESC);
204  }
205
206  @SuppressWarnings("deprecation")
207  private boolean isTableNamespaceManagerInitialized() throws IOException {
208    if (initialized) {
209      this.nsTable = this.masterServices.getConnection().getTable(TableName.NAMESPACE_TABLE_NAME);
210      return true;
211    }
212    return false;
213  }
214
215  /**
216   * Create Namespace in a blocking manner. Keeps trying until
217   * {@link ClusterSchema#HBASE_MASTER_CLUSTER_SCHEMA_OPERATION_TIMEOUT_KEY} expires. Note,
218   * by-passes notifying coprocessors and name checks. Use for system namespaces only.
219   */
220  private void blockingCreateNamespace(final NamespaceDescriptor namespaceDescriptor)
221    throws IOException {
222    ClusterSchema clusterSchema = this.masterServices.getClusterSchema();
223    long procId = clusterSchema.createNamespace(namespaceDescriptor, null,
224      ProcedurePrepareLatch.getNoopLatch());
225    block(this.masterServices, procId);
226  }
227
228  /**
229   * An ugly utility to be removed when refactor TableNamespaceManager. n
230   */
231  private static void block(final MasterServices services, final long procId)
232    throws TimeoutIOException {
233    int timeoutInMillis = services.getConfiguration().getInt(
234      ClusterSchema.HBASE_MASTER_CLUSTER_SCHEMA_OPERATION_TIMEOUT_KEY,
235      ClusterSchema.DEFAULT_HBASE_MASTER_CLUSTER_SCHEMA_OPERATION_TIMEOUT);
236    long deadlineTs = EnvironmentEdgeManager.currentTime() + timeoutInMillis;
237    ProcedureExecutor<MasterProcedureEnv> procedureExecutor = services.getMasterProcedureExecutor();
238    while (EnvironmentEdgeManager.currentTime() < deadlineTs) {
239      if (procedureExecutor.isFinished(procId)) return;
240      // Sleep some
241      Threads.sleep(10);
242    }
243    throw new TimeoutIOException("Procedure pid=" + procId + " is still running");
244  }
245
246  /**
247   * This method checks if the namespace table is assigned and then tries to create its Table
248   * reference. If it was already created before, it also makes sure that the connection isn't
249   * closed.
250   * @return true if the namespace table manager is ready to serve, false otherwise
251   */
252  @SuppressWarnings("deprecation")
253  public synchronized boolean isTableAvailableAndInitialized() throws IOException {
254    // Did we already get a table? If so, still make sure it's available
255    if (isTableNamespaceManagerInitialized()) {
256      return true;
257    }
258
259    // Now check if the table is assigned, if not then fail fast
260    if (isTableAssigned() && isTableEnabled()) {
261      try {
262        boolean initGoodSofar = true;
263        nsTable = this.masterServices.getConnection().getTable(TableName.NAMESPACE_TABLE_NAME);
264        zkNamespaceManager = new ZKNamespaceManager(masterServices.getZooKeeper());
265        zkNamespaceManager.start();
266
267        if (get(nsTable, NamespaceDescriptor.DEFAULT_NAMESPACE.getName()) == null) {
268          blockingCreateNamespace(NamespaceDescriptor.DEFAULT_NAMESPACE);
269        }
270        if (get(nsTable, NamespaceDescriptor.SYSTEM_NAMESPACE.getName()) == null) {
271          blockingCreateNamespace(NamespaceDescriptor.SYSTEM_NAMESPACE);
272        }
273
274        if (!initGoodSofar) {
275          // some required namespace is created asynchronized. We should complete init later.
276          return false;
277        }
278
279        ResultScanner scanner = nsTable.getScanner(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES);
280        try {
281          for (Result result : scanner) {
282            byte[] val = CellUtil
283              .cloneValue(result.getColumnLatestCell(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES,
284                HTableDescriptor.NAMESPACE_COL_DESC_BYTES));
285            NamespaceDescriptor ns =
286              ProtobufUtil.toNamespaceDescriptor(HBaseProtos.NamespaceDescriptor.parseFrom(val));
287            zkNamespaceManager.update(ns);
288          }
289        } finally {
290          scanner.close();
291        }
292        initialized = true;
293        return true;
294      } catch (IOException ie) {
295        LOG.warn("Caught exception in initializing namespace table manager", ie);
296        if (nsTable != null) {
297          nsTable.close();
298        }
299        throw ie;
300      }
301    }
302    return false;
303  }
304
305  private TableState getTableState() throws IOException {
306    return masterServices.getTableStateManager().getTableState(TableName.NAMESPACE_TABLE_NAME);
307  }
308
309  private boolean isTableEnabled() throws IOException {
310    return getTableState().isEnabled();
311  }
312
313  private boolean isTableAssigned() {
314    // TODO: we have a better way now (wait on event)
315    return masterServices.getAssignmentManager().getRegionStates()
316      .hasTableRegionStates(TableName.NAMESPACE_TABLE_NAME);
317  }
318
319  public void validateTableAndRegionCount(NamespaceDescriptor desc) throws IOException {
320    if (getMaxRegions(desc) <= 0) {
321      throw new ConstraintException(
322        "The max region quota for " + desc.getName() + " is less than or equal to zero.");
323    }
324    if (getMaxTables(desc) <= 0) {
325      throw new ConstraintException(
326        "The max tables quota for " + desc.getName() + " is less than or equal to zero.");
327    }
328  }
329
330  public static long getMaxTables(NamespaceDescriptor ns) throws IOException {
331    String value = ns.getConfigurationValue(KEY_MAX_TABLES);
332    long maxTables = 0;
333    if (StringUtils.isNotEmpty(value)) {
334      try {
335        maxTables = Long.parseLong(value);
336      } catch (NumberFormatException exp) {
337        throw new DoNotRetryIOException("NumberFormatException while getting max tables.", exp);
338      }
339    } else {
340      // The property is not set, so assume its the max long value.
341      maxTables = Long.MAX_VALUE;
342    }
343    return maxTables;
344  }
345
346  public static long getMaxRegions(NamespaceDescriptor ns) throws IOException {
347    String value = ns.getConfigurationValue(KEY_MAX_REGIONS);
348    long maxRegions = 0;
349    if (StringUtils.isNotEmpty(value)) {
350      try {
351        maxRegions = Long.parseLong(value);
352      } catch (NumberFormatException exp) {
353        throw new DoNotRetryIOException("NumberFormatException while getting max regions.", exp);
354      }
355    } else {
356      // The property is not set, so assume its the max long value.
357      maxRegions = Long.MAX_VALUE;
358    }
359    return maxRegions;
360  }
361
362  @Override
363  public boolean isStopped() {
364    return this.stopped;
365  }
366
367  @Override
368  public void stop(String why) {
369    if (this.stopped) {
370      return;
371    }
372    try {
373      if (this.zkNamespaceManager != null) {
374        this.zkNamespaceManager.stop();
375      }
376    } catch (IOException ioe) {
377      LOG.warn("Failed NamespaceManager close", ioe);
378    }
379    try {
380      if (this.nsTable != null) {
381        this.nsTable.close();
382      }
383    } catch (IOException ioe) {
384      LOG.warn("Failed Namespace Table close", ioe);
385    }
386    this.stopped = true;
387  }
388}