View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.master;
20  
21  import java.io.IOException;
22  import java.io.InterruptedIOException;
23  import java.util.NavigableSet;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.hbase.CellUtil;
31  import org.apache.hadoop.hbase.DoNotRetryIOException;
32  import org.apache.hadoop.hbase.HConstants;
33  import org.apache.hadoop.hbase.HTableDescriptor;
34  import org.apache.hadoop.hbase.NamespaceDescriptor;
35  import org.apache.hadoop.hbase.TableName;
36  import org.apache.hadoop.hbase.ZKNamespaceManager;
37  import org.apache.hadoop.hbase.MetaTableAccessor;
38  import org.apache.hadoop.hbase.client.Delete;
39  import org.apache.hadoop.hbase.client.Get;
40  import org.apache.hadoop.hbase.client.Put;
41  import org.apache.hadoop.hbase.client.Result;
42  import org.apache.hadoop.hbase.client.ResultScanner;
43  import org.apache.hadoop.hbase.client.Table;
44  import org.apache.hadoop.hbase.constraint.ConstraintException;
45  import org.apache.hadoop.hbase.master.procedure.CreateNamespaceProcedure;
46  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
47  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
48  import org.apache.hadoop.hbase.util.Bytes;
49  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
50  
51  import com.google.common.collect.Sets;
52  
53  /**
54   * This is a helper class used to manage the namespace
55   * metadata that is stored in TableName.NAMESPACE_TABLE_NAME
56   * It also mirrors updates to the ZK store by forwarding updates to
57   * {@link org.apache.hadoop.hbase.ZKNamespaceManager}
58   */
59  @InterfaceAudience.Private
60  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="IS2_INCONSISTENT_SYNC",
61    justification="TODO: synchronize access on nsTable but it is done in tiers above and this " +
62      "class is going away/shrinking")
63  public class TableNamespaceManager {
64    private static final Log LOG = LogFactory.getLog(TableNamespaceManager.class);
65  
66    private Configuration conf;
67    private MasterServices masterServices;
68    private Table nsTable = null; // FindBugs: IS2_INCONSISTENT_SYNC TODO: Access is not synchronized
69    private ZKNamespaceManager zkNamespaceManager;
70    private boolean initialized;
71  
72    public static final String KEY_MAX_REGIONS = "hbase.namespace.quota.maxregions";
73    public static final String KEY_MAX_TABLES = "hbase.namespace.quota.maxtables";
74    static final String NS_INIT_TIMEOUT = "hbase.master.namespace.init.timeout";
75    static final int DEFAULT_NS_INIT_TIMEOUT = 300000;
76  
77    public TableNamespaceManager(MasterServices masterServices) {
78      this.masterServices = masterServices;
79      this.conf = masterServices.getConfiguration();
80    }
81  
82    public void start() throws IOException {
83      if (!MetaTableAccessor.tableExists(masterServices.getConnection(),
84        TableName.NAMESPACE_TABLE_NAME)) {
85        LOG.info("Namespace table not found. Creating...");
86        createNamespaceTable(masterServices);
87      }
88  
89      try {
90        // Wait for the namespace table to be assigned.
91        // If timed out, we will move ahead without initializing it.
92        // So that it should be initialized later on lazily.
93        long startTime = EnvironmentEdgeManager.currentTime();
94        int timeout = conf.getInt(NS_INIT_TIMEOUT, DEFAULT_NS_INIT_TIMEOUT);
95        while (!isTableAvailableAndInitialized(false)) {
96          if (EnvironmentEdgeManager.currentTime() - startTime + 100 > timeout) {
97            // We can't do anything if ns is not online.
98            throw new IOException("Timedout " + timeout + "ms waiting for namespace table to " +
99              "be assigned");
100         }
101         Thread.sleep(100);
102       }
103     } catch (InterruptedException e) {
104       throw (InterruptedIOException) new InterruptedIOException().initCause(e);
105     }
106   }
107 
108   private synchronized Table getNamespaceTable() throws IOException {
109     if (!isTableNamespaceManagerInitialized()) {
110       throw new IOException(this.getClass().getName() + " isn't ready to serve");
111     }
112     return nsTable;
113   }
114 
115   /*
116    * check whether a namespace has already existed.
117    */
118   public boolean doesNamespaceExist(final String namespaceName) throws IOException {
119     if (nsTable == null) {
120       throw new IOException(this.getClass().getName() + " isn't ready to serve");
121     }
122     return (get(nsTable, namespaceName) != null);
123   }
124 
125   public synchronized NamespaceDescriptor get(String name) throws IOException {
126     if (!isTableNamespaceManagerInitialized()) {
127       return null;
128     }
129     return zkNamespaceManager.get(name);
130   }
131 
132   private NamespaceDescriptor get(Table table, String name) throws IOException {
133     Result res = table.get(new Get(Bytes.toBytes(name)));
134     if (res.isEmpty()) {
135       return null;
136     }
137     byte[] val = CellUtil.cloneValue(res.getColumnLatestCell(
138         HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES, HTableDescriptor.NAMESPACE_COL_DESC_BYTES));
139     return
140         ProtobufUtil.toNamespaceDescriptor(
141             HBaseProtos.NamespaceDescriptor.parseFrom(val));
142   }
143 
144   public void insertIntoNSTable(final NamespaceDescriptor ns) throws IOException {
145     if (nsTable == null) {
146       throw new IOException(this.getClass().getName() + " isn't ready to serve");
147     }
148     Put p = new Put(Bytes.toBytes(ns.getName()));
149     p.addImmutable(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES,
150         HTableDescriptor.NAMESPACE_COL_DESC_BYTES,
151         ProtobufUtil.toProtoNamespaceDescriptor(ns).toByteArray());
152     nsTable.put(p);
153   }
154 
155   public void updateZKNamespaceManager(final NamespaceDescriptor ns) throws IOException {
156     try {
157       zkNamespaceManager.update(ns);
158     } catch (IOException ex) {
159       String msg = "Failed to update namespace information in ZK.";
160       LOG.error(msg, ex);
161       throw new IOException(msg, ex);
162     }
163   }
164 
165   public void removeFromNSTable(final String namespaceName) throws IOException {
166     if (nsTable == null) {
167       throw new IOException(this.getClass().getName() + " isn't ready to serve");
168     }
169     Delete d = new Delete(Bytes.toBytes(namespaceName));
170     nsTable.delete(d);
171   }
172 
173   public void removeFromZKNamespaceManager(final String namespaceName) throws IOException {
174     zkNamespaceManager.remove(namespaceName);
175   }
176 
177   public synchronized NavigableSet<NamespaceDescriptor> list() throws IOException {
178     NavigableSet<NamespaceDescriptor> ret =
179         Sets.newTreeSet(NamespaceDescriptor.NAMESPACE_DESCRIPTOR_COMPARATOR);
180     ResultScanner scanner =
181         getNamespaceTable().getScanner(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES);
182     try {
183       for(Result r : scanner) {
184         byte[] val = CellUtil.cloneValue(r.getColumnLatestCell(
185           HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES,
186           HTableDescriptor.NAMESPACE_COL_DESC_BYTES));
187         ret.add(ProtobufUtil.toNamespaceDescriptor(
188             HBaseProtos.NamespaceDescriptor.parseFrom(val)));
189       }
190     } finally {
191       scanner.close();
192     }
193     return ret;
194   }
195 
196   private void createNamespaceTable(MasterServices masterServices) throws IOException {
197     masterServices.createSystemTable(HTableDescriptor.NAMESPACE_TABLEDESC);
198   }
199 
200   @SuppressWarnings("deprecation")
201   private boolean isTableNamespaceManagerInitialized() throws IOException {
202     if (initialized) {
203       this.nsTable = this.masterServices.getConnection().getTable(TableName.NAMESPACE_TABLE_NAME);
204       return true;
205     }
206     return false;
207   }
208 
209   /**
210    * This method checks if the namespace table is assigned and then
211    * tries to create its HTable. If it was already created before, it also makes
212    * sure that the connection isn't closed.
213    * @return true if the namespace table manager is ready to serve, false
214    * otherwise
215    * @throws IOException
216    */
217   @SuppressWarnings("deprecation")
218   public synchronized boolean isTableAvailableAndInitialized(
219       final boolean createNamespaceAync) throws IOException {
220     // Did we already get a table? If so, still make sure it's available
221     if (isTableNamespaceManagerInitialized()) {
222       return true;
223     }
224 
225     if (zkNamespaceManager == null) {
226       zkNamespaceManager = new ZKNamespaceManager(masterServices.getZooKeeper());
227       zkNamespaceManager.start();
228     }
229 
230     // Now check if the table is assigned, if not then fail fast
231     if (isTableAssigned()) {
232       try {
233         boolean initGoodSofar = true;
234         nsTable = this.masterServices.getConnection().getTable(TableName.NAMESPACE_TABLE_NAME);
235         if (get(nsTable, NamespaceDescriptor.DEFAULT_NAMESPACE.getName()) == null) {
236           if (createNamespaceAync) {
237             masterServices.getMasterProcedureExecutor().submitProcedure(
238               new CreateNamespaceProcedure(
239                 masterServices.getMasterProcedureExecutor().getEnvironment(),
240                 NamespaceDescriptor.DEFAULT_NAMESPACE));
241             initGoodSofar = false;
242           }
243           else {
244             masterServices.createNamespaceSync(
245               NamespaceDescriptor.DEFAULT_NAMESPACE,
246               HConstants.NO_NONCE,
247               HConstants.NO_NONCE,
248               false);
249           }
250         }
251         if (get(nsTable, NamespaceDescriptor.SYSTEM_NAMESPACE.getName()) == null) {
252           if (createNamespaceAync) {
253             masterServices.getMasterProcedureExecutor().submitProcedure(
254               new CreateNamespaceProcedure(
255                 masterServices.getMasterProcedureExecutor().getEnvironment(),
256                 NamespaceDescriptor.SYSTEM_NAMESPACE));
257             initGoodSofar = false;
258           }
259           else {
260             masterServices.createNamespaceSync(
261               NamespaceDescriptor.SYSTEM_NAMESPACE,
262               HConstants.NO_NONCE,
263               HConstants.NO_NONCE,
264               false);
265           }
266         }
267 
268         if (!initGoodSofar) {
269           // some required namespace is created asynchronized. We should complete init later.
270           return false;
271         }
272 
273         ResultScanner scanner = nsTable.getScanner(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES);
274         try {
275           for (Result result : scanner) {
276             byte[] val =  CellUtil.cloneValue(result.getColumnLatestCell(
277                 HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES,
278                 HTableDescriptor.NAMESPACE_COL_DESC_BYTES));
279             NamespaceDescriptor ns =
280                 ProtobufUtil.toNamespaceDescriptor(
281                     HBaseProtos.NamespaceDescriptor.parseFrom(val));
282             zkNamespaceManager.update(ns);
283           }
284         } finally {
285           scanner.close();
286         }
287         initialized = true;
288         return true;
289       } catch (IOException ie) {
290         LOG.warn("Caught exception in initializing namespace table manager", ie);
291         if (nsTable != null) {
292           nsTable.close();
293         }
294         throw ie;
295       }
296     }
297     return false;
298   }
299 
300   private boolean isTableAssigned() {
301     return !masterServices.getAssignmentManager().getRegionStates().
302         getRegionsOfTable(TableName.NAMESPACE_TABLE_NAME).isEmpty();
303   }
304 
305   public void validateTableAndRegionCount(NamespaceDescriptor desc) throws IOException {
306     if (getMaxRegions(desc) <= 0) {
307       throw new ConstraintException("The max region quota for " + desc.getName()
308           + " is less than or equal to zero.");
309     }
310     if (getMaxTables(desc) <= 0) {
311       throw new ConstraintException("The max tables quota for " + desc.getName()
312           + " is less than or equal to zero.");
313     }
314   }
315 
316   public static long getMaxTables(NamespaceDescriptor ns) throws IOException {
317     String value = ns.getConfigurationValue(KEY_MAX_TABLES);
318     long maxTables = 0;
319     if (StringUtils.isNotEmpty(value)) {
320       try {
321         maxTables = Long.parseLong(value);
322       } catch (NumberFormatException exp) {
323         throw new DoNotRetryIOException("NumberFormatException while getting max tables.", exp);
324       }
325     } else {
326       // The property is not set, so assume its the max long value.
327       maxTables = Long.MAX_VALUE;
328     }
329     return maxTables;
330   }
331 
332   public static long getMaxRegions(NamespaceDescriptor ns) throws IOException {
333     String value = ns.getConfigurationValue(KEY_MAX_REGIONS);
334     long maxRegions = 0;
335     if (StringUtils.isNotEmpty(value)) {
336       try {
337         maxRegions = Long.parseLong(value);
338       } catch (NumberFormatException exp) {
339         throw new DoNotRetryIOException("NumberFormatException while getting max regions.", exp);
340       }
341     } else {
342       // The property is not set, so assume its the max long value.
343       maxRegions = Long.MAX_VALUE;
344     }
345     return maxRegions;
346   }
347 }