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