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