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