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