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.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.InterruptedIOException;
24  import java.util.NavigableSet;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.fs.FileStatus;
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.hbase.CellUtil;
34  import org.apache.hadoop.hbase.DoNotRetryIOException;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HRegionInfo;
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.NamespaceDescriptor;
39  import org.apache.hadoop.hbase.NamespaceExistException;
40  import org.apache.hadoop.hbase.NamespaceNotFoundException;
41  import org.apache.hadoop.hbase.TableName;
42  import org.apache.hadoop.hbase.ZKNamespaceManager;
43  import org.apache.hadoop.hbase.MetaTableAccessor;
44  import org.apache.hadoop.hbase.client.Delete;
45  import org.apache.hadoop.hbase.client.Get;
46  import org.apache.hadoop.hbase.client.Put;
47  import org.apache.hadoop.hbase.client.Result;
48  import org.apache.hadoop.hbase.client.ResultScanner;
49  import org.apache.hadoop.hbase.client.Table;
50  import org.apache.hadoop.hbase.constraint.ConstraintException;
51  import org.apache.hadoop.hbase.master.handler.CreateTableHandler;
52  import org.apache.hadoop.hbase.master.procedure.CreateTableProcedure;
53  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
54  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
55  import org.apache.hadoop.hbase.util.Bytes;
56  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
57  import org.apache.hadoop.hbase.util.FSUtils;
58  
59  import com.google.common.annotations.VisibleForTesting;
60  import com.google.common.collect.Sets;
61  
62  /**
63   * This is a helper class used to manage the namespace
64   * metadata that is stored in TableName.NAMESPACE_TABLE_NAME
65   * It also mirrors updates to the ZK store by forwarding updates to
66   * {@link org.apache.hadoop.hbase.ZKNamespaceManager}
67   */
68  @InterfaceAudience.Private
69  public class TableNamespaceManager {
70    private static final Log LOG = LogFactory.getLog(TableNamespaceManager.class);
71  
72    private Configuration conf;
73    private MasterServices masterServices;
74    private Table nsTable;
75    private ZKNamespaceManager zkNamespaceManager;
76    private boolean initialized;
77    
78    public static final String KEY_MAX_REGIONS = "hbase.namespace.quota.maxregions";
79    public static final String KEY_MAX_TABLES = "hbase.namespace.quota.maxtables";
80  
81    static final String NS_INIT_TIMEOUT = "hbase.master.namespace.init.timeout";
82    static final int DEFAULT_NS_INIT_TIMEOUT = 1800000; // default is 30 minutes
83    private static final int WAIT_MESSAGE_TO_PRINTOUT = 300000; // print out message every 5 minutes
84  
85    public TableNamespaceManager(MasterServices masterServices) {
86      this.masterServices = masterServices;
87      this.conf = masterServices.getConfiguration();
88    }
89  
90    public void start() throws IOException {
91      if (!MetaTableAccessor.tableExists(masterServices.getConnection(),
92          TableName.NAMESPACE_TABLE_NAME)) {
93        LOG.info("Namespace table not found. Creating...");
94        createNamespaceTable(masterServices);
95      }
96  
97      try {
98        // Wait for the namespace table to be assigned.
99        // If timed out, we will move ahead without initializing it.
100       // So that it should be initialized later on lazily.
101       long startTime = EnvironmentEdgeManager.currentTime();
102       long msgCount = 0;
103 	  long waitTime;
104       int timeout = conf.getInt(NS_INIT_TIMEOUT, DEFAULT_NS_INIT_TIMEOUT);
105       while (!isTableAssigned()) {
106         waitTime = EnvironmentEdgeManager.currentTime() - startTime;
107     	if (waitTime > timeout) {
108           // We can't do anything if ns is not online.
109           throw new IOException("Timedout " + timeout + "ms waiting for namespace table to " +
110             "be assigned");
111         } else if (waitTime > msgCount * WAIT_MESSAGE_TO_PRINTOUT) {
112           LOG.info("Waiting for namespace table to be online. Time waited = " + waitTime + " ms.");
113           msgCount++;
114         }
115         Thread.sleep(100);
116       }
117     } catch (InterruptedException e) {
118       throw (InterruptedIOException)new InterruptedIOException().initCause(e);
119     }
120 
121     // initialize namespace table
122     isTableAvailableAndInitialized();
123   }
124 
125   @VisibleForTesting
126   public boolean isTableNamespaceManagerStarted() {
127     return initialized;
128   }
129   
130   private synchronized Table getNamespaceTable() throws IOException {
131     if (!isTableAvailableAndInitialized()) {
132       throw new IOException(this.getClass().getName() + " isn't ready to serve");
133     }
134     return nsTable;
135   }
136 
137 
138   public synchronized NamespaceDescriptor get(String name) throws IOException {
139     if (!isTableAvailableAndInitialized()) return null;
140     return zkNamespaceManager.get(name);
141   }
142 
143   public synchronized void create(NamespaceDescriptor ns) throws IOException {
144     create(getNamespaceTable(), ns);
145   }
146 
147   public synchronized void update(NamespaceDescriptor ns) throws IOException {
148     Table table = getNamespaceTable();
149     if (get(table, ns.getName()) == null) {
150       throw new NamespaceNotFoundException(ns.getName());
151     }
152     upsert(table, ns);
153   }
154 
155   private NamespaceDescriptor get(Table table, String name) throws IOException {
156     Result res = table.get(new Get(Bytes.toBytes(name)));
157     if (res.isEmpty()) {
158       return null;
159     }
160     byte[] val = CellUtil.cloneValue(res.getColumnLatestCell(
161         HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES, HTableDescriptor.NAMESPACE_COL_DESC_BYTES));
162     return
163         ProtobufUtil.toNamespaceDescriptor(
164             HBaseProtos.NamespaceDescriptor.parseFrom(val));
165   }
166 
167   private void create(Table table, NamespaceDescriptor ns) throws IOException {
168     if (get(table, ns.getName()) != null) {
169       throw new NamespaceExistException(ns.getName());
170     }
171     validateTableAndRegionCount(ns);
172     FileSystem fs = masterServices.getMasterFileSystem().getFileSystem();
173     fs.mkdirs(FSUtils.getNamespaceDir(
174         masterServices.getMasterFileSystem().getRootDir(), ns.getName()));
175     upsert(table, ns);
176     if (this.masterServices.isInitialized()) {
177       this.masterServices.getMasterQuotaManager().setNamespaceQuota(ns);
178     }
179   }
180 
181   private void upsert(Table table, NamespaceDescriptor ns) throws IOException {
182     validateTableAndRegionCount(ns);
183     Put p = new Put(Bytes.toBytes(ns.getName()));
184     p.addImmutable(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES,
185         HTableDescriptor.NAMESPACE_COL_DESC_BYTES,
186         ProtobufUtil.toProtoNamespaceDescriptor(ns).toByteArray());
187     table.put(p);
188     try {
189       zkNamespaceManager.update(ns);
190     } catch(IOException ex) {
191       String msg = "Failed to update namespace information in ZK. Aborting.";
192       LOG.fatal(msg, ex);
193       masterServices.abort(msg, ex);
194     }
195   }
196 
197   public synchronized void remove(String name) throws IOException {
198     if (get(name) == null) {
199       throw new NamespaceNotFoundException(name);
200     }
201     if (NamespaceDescriptor.RESERVED_NAMESPACES.contains(name)) {
202       throw new ConstraintException("Reserved namespace "+name+" cannot be removed.");
203     }
204     int tableCount;
205     try {
206       tableCount = masterServices.listTableDescriptorsByNamespace(name).size();
207     } catch (FileNotFoundException fnfe) {
208       throw new NamespaceNotFoundException(name);
209     }
210     if (tableCount > 0) {
211       throw new ConstraintException("Only empty namespaces can be removed. " +
212           "Namespace "+name+" has "+tableCount+" tables");
213     }
214     Delete d = new Delete(Bytes.toBytes(name));
215     getNamespaceTable().delete(d);
216     //don't abort if cleanup isn't complete
217     //it will be replaced on new namespace creation
218     zkNamespaceManager.remove(name);
219     FileSystem fs = masterServices.getMasterFileSystem().getFileSystem();
220     for(FileStatus status :
221             fs.listStatus(FSUtils.getNamespaceDir(
222                 masterServices.getMasterFileSystem().getRootDir(), name))) {
223       if (!HConstants.HBASE_NON_TABLE_DIRS.contains(status.getPath().getName())) {
224         throw new IOException("Namespace directory contains table dir: "+status.getPath());
225       }
226     }
227     if (!fs.delete(FSUtils.getNamespaceDir(
228         masterServices.getMasterFileSystem().getRootDir(), name), true)) {
229       throw new IOException("Failed to remove namespace: "+name);
230     }
231     this.masterServices.getMasterQuotaManager().removeNamespaceQuota(name);
232   }
233 
234   public synchronized NavigableSet<NamespaceDescriptor> list() throws IOException {
235     NavigableSet<NamespaceDescriptor> ret =
236         Sets.newTreeSet(NamespaceDescriptor.NAMESPACE_DESCRIPTOR_COMPARATOR);
237     ResultScanner scanner = getNamespaceTable().getScanner(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES);
238     try {
239       for(Result r : scanner) {
240         byte[] val = CellUtil.cloneValue(r.getColumnLatestCell(
241           HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES,
242           HTableDescriptor.NAMESPACE_COL_DESC_BYTES));
243         ret.add(ProtobufUtil.toNamespaceDescriptor(
244             HBaseProtos.NamespaceDescriptor.parseFrom(val)));
245       }
246     } finally {
247       scanner.close();
248     }
249     return ret;
250   }
251 
252   private void createNamespaceTable(MasterServices masterServices) throws IOException {
253     HRegionInfo[] newRegions = new HRegionInfo[]{
254         new HRegionInfo(HTableDescriptor.NAMESPACE_TABLEDESC.getTableName(), null, null)};
255 
256     if (masterServices.isMasterProcedureExecutorEnabled()) {
257       // we need to create the table this way to bypass checkInitialized
258       masterServices.getMasterProcedureExecutor()
259         .submitProcedure(new CreateTableProcedure(
260           masterServices.getMasterProcedureExecutor().getEnvironment(),
261           HTableDescriptor.NAMESPACE_TABLEDESC,
262           newRegions));
263     } else {
264       masterServices.getExecutorService()
265           .submit(new CreateTableHandler(masterServices,
266               masterServices.getMasterFileSystem(),
267               HTableDescriptor.NAMESPACE_TABLEDESC,
268               masterServices.getConfiguration(),
269               newRegions,
270               masterServices).prepare());
271     }
272   }
273 
274   /**
275    * This method checks if the namespace table is assigned and then
276    * tries to create its HTable. If it was already created before, it also makes
277    * sure that the connection isn't closed.
278    * @return true if the namespace table manager is ready to serve, false
279    * otherwise
280    * @throws IOException
281    */
282   @SuppressWarnings("deprecation")
283   public synchronized boolean isTableAvailableAndInitialized() throws IOException {
284     // Did we already get a table? If so, still make sure it's available
285     if (initialized) {
286       this.nsTable = this.masterServices.getConnection().getTable(TableName.NAMESPACE_TABLE_NAME);
287       return true;
288     }
289 
290     // Now check if the table is assigned, if not then fail fast
291     if (isTableAssigned()) {
292       try {
293         nsTable = this.masterServices.getConnection().getTable(TableName.NAMESPACE_TABLE_NAME);
294         zkNamespaceManager = new ZKNamespaceManager(masterServices.getZooKeeper());
295         zkNamespaceManager.start();
296 
297         if (get(nsTable, NamespaceDescriptor.DEFAULT_NAMESPACE.getName()) == null) {
298           create(nsTable, NamespaceDescriptor.DEFAULT_NAMESPACE);
299         }
300         if (get(nsTable, NamespaceDescriptor.SYSTEM_NAMESPACE.getName()) == null) {
301           create(nsTable, NamespaceDescriptor.SYSTEM_NAMESPACE);
302         }
303 
304         ResultScanner scanner = nsTable.getScanner(HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES);
305         try {
306           for (Result result : scanner) {
307             byte[] val =  CellUtil.cloneValue(result.getColumnLatest(
308                 HTableDescriptor.NAMESPACE_FAMILY_INFO_BYTES,
309                 HTableDescriptor.NAMESPACE_COL_DESC_BYTES));
310             NamespaceDescriptor ns =
311                 ProtobufUtil.toNamespaceDescriptor(
312                     HBaseProtos.NamespaceDescriptor.parseFrom(val));
313             zkNamespaceManager.update(ns);
314           }
315         } finally {
316           scanner.close();
317         }
318         initialized = true;
319         return true;
320       } catch (IOException ie) {
321         LOG.warn("Caught exception in initializing namespace table manager", ie);
322         if (nsTable != null) {
323           nsTable.close();
324         }
325         throw ie;
326       }
327     }
328     return false;
329   }
330 
331   private boolean isTableAssigned() {
332     return !masterServices.getAssignmentManager().getRegionStates().
333       getRegionsOfTable(TableName.NAMESPACE_TABLE_NAME).isEmpty();
334   }
335   
336   void validateTableAndRegionCount(NamespaceDescriptor desc) throws IOException {
337     if (getMaxRegions(desc) <= 0) {
338       throw new ConstraintException("The max region quota for " + desc.getName()
339           + " is less than or equal to zero.");
340     }
341     if (getMaxTables(desc) <= 0) {
342       throw new ConstraintException("The max tables quota for " + desc.getName()
343           + " is less than or equal to zero.");
344     }
345   }
346 
347   public static long getMaxTables(NamespaceDescriptor ns) throws IOException {
348     String value = ns.getConfigurationValue(KEY_MAX_TABLES);
349     long maxTables = 0;
350     if (StringUtils.isNotEmpty(value)) {
351       try {
352         maxTables = Long.parseLong(value);
353       } catch (NumberFormatException exp) {
354         throw new DoNotRetryIOException("NumberFormatException while getting max tables.", exp);
355       }
356     } else {
357       // The property is not set, so assume its the max long value.
358       maxTables = Long.MAX_VALUE;
359     }
360     return maxTables;
361   }
362 
363   public static long getMaxRegions(NamespaceDescriptor ns) throws IOException {
364     String value = ns.getConfigurationValue(KEY_MAX_REGIONS);
365     long maxRegions = 0;
366     if (StringUtils.isNotEmpty(value)) {
367       try {
368         maxRegions = Long.parseLong(value);
369       } catch (NumberFormatException exp) {
370         throw new DoNotRetryIOException("NumberFormatException while getting max regions.", exp);
371       }
372     } else {
373       // The property is not set, so assume its the max long value.
374       maxRegions = Long.MAX_VALUE;
375     }
376     return maxRegions;
377   }
378 }