View Javadoc

1   /**
2    * Copyright 2007 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase;
21  
22  import java.io.DataInput;
23  import java.io.DataOutput;
24  import java.io.IOException;
25  import java.util.Arrays;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.KeyValue.KVComparator;
30  import org.apache.hadoop.hbase.util.Bytes;
31  import org.apache.hadoop.hbase.util.JenkinsHash;
32  import org.apache.hadoop.hbase.util.MD5Hash;
33  import org.apache.hadoop.io.VersionedWritable;
34  import org.apache.hadoop.io.WritableComparable;
35  
36  /**
37   * HRegion information.
38   * Contains HRegion id, start and end keys, a reference to this
39   * HRegions' table descriptor, etc.
40   */
41  public class HRegionInfo extends VersionedWritable implements WritableComparable<HRegionInfo>{
42    private static final byte VERSION = 0;
43    private static final Log LOG = LogFactory.getLog(HRegionInfo.class);
44  
45    /**
46     * The new format for a region name contains its encodedName at the end.
47     * The encoded name also serves as the directory name for the region
48     * in the filesystem.
49     *
50     * New region name format:
51     *    &lt;tablename>,,&lt;startkey>,&lt;regionIdTimestamp>.&lt;encodedName>.
52     * where,
53     *    &lt;encodedName> is a hex version of the MD5 hash of
54     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
55     * 
56     * The old region name format:
57     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
58     * For region names in the old format, the encoded name is a 32-bit
59     * JenkinsHash integer value (in its decimal notation, string form). 
60     *<p>
61     * **NOTE**
62     *
63     * ROOT, the first META region, and regions created by an older
64     * version of HBase (0.20 or prior) will continue to use the
65     * old region name format.
66     */
67  
68    /** Separator used to demarcate the encodedName in a region name
69     * in the new format. See description on new format above. 
70     */ 
71    private static final int ENC_SEPARATOR = '.';
72    public  static final int MD5_HEX_LENGTH   = 32;
73  
74    /**
75     * Does region name contain its encoded name?
76     * @param regionName region name
77     * @return boolean indicating if this a new format region
78     *         name which contains its encoded name.
79     */
80    private static boolean hasEncodedName(final byte[] regionName) {
81      // check if region name ends in ENC_SEPARATOR
82      if ((regionName.length >= 1)
83          && (regionName[regionName.length - 1] == ENC_SEPARATOR)) {
84        // region name is new format. it contains the encoded name.
85        return true; 
86      }
87      return false;
88    }
89    
90    /**
91     * @param regionName
92     * @return the encodedName
93     */
94    public static String encodeRegionName(final byte [] regionName) {
95      String encodedName;
96      if (hasEncodedName(regionName)) {
97        // region is in new format:
98        // <tableName>,<startKey>,<regionIdTimeStamp>/encodedName/
99        encodedName = Bytes.toString(regionName,
100           regionName.length - MD5_HEX_LENGTH - 1,
101           MD5_HEX_LENGTH);
102     } else {
103       // old format region name. ROOT and first META region also 
104       // use this format.EncodedName is the JenkinsHash value.
105       int hashVal = Math.abs(JenkinsHash.getInstance().hash(regionName,
106                                                             regionName.length,
107                                                             0));
108       encodedName = String.valueOf(hashVal);
109     }
110     return encodedName;
111   }
112 
113   /** delimiter used between portions of a region name */
114   public static final int DELIMITER = ',';
115 
116   /** HRegionInfo for root region */
117   public static final HRegionInfo ROOT_REGIONINFO =
118     new HRegionInfo(0L, HTableDescriptor.ROOT_TABLEDESC);
119 
120   /** HRegionInfo for first meta region */
121   public static final HRegionInfo FIRST_META_REGIONINFO =
122     new HRegionInfo(1L, HTableDescriptor.META_TABLEDESC);
123 
124   private byte [] endKey = HConstants.EMPTY_BYTE_ARRAY;
125   private boolean offLine = false;
126   private long regionId = -1;
127   private transient byte [] regionName = HConstants.EMPTY_BYTE_ARRAY;
128   private String regionNameStr = "";
129   private boolean split = false;
130   private byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
131   protected HTableDescriptor tableDesc = null;
132   private int hashCode = -1;
133   //TODO: Move NO_HASH to HStoreFile which is really the only place it is used.
134   public static final String NO_HASH = null;
135   private volatile String encodedName = NO_HASH;
136 
137   private void setHashCode() {
138     int result = Arrays.hashCode(this.regionName);
139     result ^= this.regionId;
140     result ^= Arrays.hashCode(this.startKey);
141     result ^= Arrays.hashCode(this.endKey);
142     result ^= Boolean.valueOf(this.offLine).hashCode();
143     result ^= this.tableDesc.hashCode();
144     this.hashCode = result;
145   }
146 
147   /**
148    * Private constructor used constructing HRegionInfo for the catalog root and
149    * first meta regions
150    */
151   private HRegionInfo(long regionId, HTableDescriptor tableDesc) {
152     super();
153     this.regionId = regionId;
154     this.tableDesc = tableDesc;
155     
156     // Note: Root & First Meta regions names are still in old format   
157     this.regionName = createRegionName(tableDesc.getName(), null,
158                                        regionId, false);
159     this.regionNameStr = Bytes.toStringBinary(this.regionName);
160     setHashCode();
161   }
162 
163   /** Default constructor - creates empty object */
164   public HRegionInfo() {
165     super();
166     this.tableDesc = new HTableDescriptor();
167   }
168 
169   /**
170    * Construct HRegionInfo with explicit parameters
171    *
172    * @param tableDesc the table descriptor
173    * @param startKey first key in region
174    * @param endKey end of key range
175    * @throws IllegalArgumentException
176    */
177   public HRegionInfo(final HTableDescriptor tableDesc, final byte [] startKey,
178       final byte [] endKey)
179   throws IllegalArgumentException {
180     this(tableDesc, startKey, endKey, false);
181   }
182 
183   /**
184    * Construct HRegionInfo with explicit parameters
185    *
186    * @param tableDesc the table descriptor
187    * @param startKey first key in region
188    * @param endKey end of key range
189    * @param split true if this region has split and we have daughter regions
190    * regions that may or may not hold references to this region.
191    * @throws IllegalArgumentException
192    */
193   public HRegionInfo(HTableDescriptor tableDesc, final byte [] startKey,
194       final byte [] endKey, final boolean split)
195   throws IllegalArgumentException {
196     this(tableDesc, startKey, endKey, split, System.currentTimeMillis());
197   }
198 
199   /**
200    * Construct HRegionInfo with explicit parameters
201    *
202    * @param tableDesc the table descriptor
203    * @param startKey first key in region
204    * @param endKey end of key range
205    * @param split true if this region has split and we have daughter regions
206    * regions that may or may not hold references to this region.
207    * @param regionid Region id to use.
208    * @throws IllegalArgumentException
209    */
210   public HRegionInfo(HTableDescriptor tableDesc, final byte [] startKey,
211     final byte [] endKey, final boolean split, final long regionid)
212   throws IllegalArgumentException {
213     super();
214     if (tableDesc == null) {
215       throw new IllegalArgumentException("tableDesc cannot be null");
216     }
217     this.offLine = false;
218     this.regionId = regionid;
219     this.regionName = createRegionName(tableDesc.getName(), startKey, regionId, true);
220     this.regionNameStr = Bytes.toStringBinary(this.regionName);
221     this.split = split;
222     this.endKey = endKey == null? HConstants.EMPTY_END_ROW: endKey.clone();
223     this.startKey = startKey == null?
224       HConstants.EMPTY_START_ROW: startKey.clone();
225     this.tableDesc = tableDesc;
226     setHashCode();
227   }
228 
229   /**
230    * Costruct a copy of another HRegionInfo
231    *
232    * @param other
233    */
234   public HRegionInfo(HRegionInfo other) {
235     super();
236     this.endKey = other.getEndKey();
237     this.offLine = other.isOffline();
238     this.regionId = other.getRegionId();
239     this.regionName = other.getRegionName();
240     this.regionNameStr = Bytes.toStringBinary(this.regionName);
241     this.split = other.isSplit();
242     this.startKey = other.getStartKey();
243     this.tableDesc = other.getTableDesc();
244     this.hashCode = other.hashCode();
245     this.encodedName = other.getEncodedName();
246   }
247 
248   private static byte [] createRegionName(final byte [] tableName,
249       final byte [] startKey, final long regionid, boolean newFormat) {
250     return createRegionName(tableName, startKey, Long.toString(regionid), newFormat);
251   }
252 
253   /**
254    * Make a region name of passed parameters.
255    * @param tableName
256    * @param startKey Can be null
257    * @param id Region id.
258    * @param newFormat should we create the region name in the new format
259    *                  (such that it contains its encoded name?).
260    * @return Region name made of passed tableName, startKey and id
261    */
262   public static byte [] createRegionName(final byte [] tableName,
263       final byte [] startKey, final String id, boolean newFormat) {
264     return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat);
265   }
266   /**
267    * Make a region name of passed parameters.
268    * @param tableName
269    * @param startKey Can be null
270    * @param id Region id
271    * @param newFormat should we create the region name in the new format
272    *                  (such that it contains its encoded name?).
273    * @return Region name made of passed tableName, startKey and id
274    */
275   public static byte [] createRegionName(final byte [] tableName,
276       final byte [] startKey, final byte [] id, boolean newFormat) {
277     byte [] b = new byte [tableName.length + 2 + id.length +
278        (startKey == null? 0: startKey.length) +
279        (newFormat ? (MD5_HEX_LENGTH + 2) : 0)];
280 
281     int offset = tableName.length;
282     System.arraycopy(tableName, 0, b, 0, offset);
283     b[offset++] = DELIMITER;
284     if (startKey != null && startKey.length > 0) {
285       System.arraycopy(startKey, 0, b, offset, startKey.length);
286       offset += startKey.length;
287     }
288     b[offset++] = DELIMITER;
289     System.arraycopy(id, 0, b, offset, id.length);
290     offset += id.length;
291 
292     if (newFormat) {
293       //
294       // Encoded name should be built into the region name.
295       //
296       // Use the region name thus far (namely, <tablename>,<startKey>,<id>)
297       // to compute a MD5 hash to be used as the encoded name, and append
298       // it to the byte buffer.
299       //
300       String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset);
301       byte [] md5HashBytes = Bytes.toBytes(md5Hash);
302 
303       if (md5HashBytes.length != MD5_HEX_LENGTH) {
304         LOG.error("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH +
305                   "; Got=" + md5HashBytes.length); 
306       }
307 
308       // now append the bytes '.<encodedName>.' to the end
309       b[offset++] = ENC_SEPARATOR;
310       System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH);
311       offset += MD5_HEX_LENGTH;
312       b[offset++] = ENC_SEPARATOR;
313     }
314     
315     return b;
316   }
317 
318   /**
319    * Separate elements of a regionName.
320    * @param regionName
321    * @return Array of byte[] containing tableName, startKey and id
322    * @throws IOException
323    */
324   public static byte [][] parseRegionName(final byte [] regionName)
325   throws IOException {
326     int offset = -1;
327     for (int i = 0; i < regionName.length; i++) {
328       if (regionName[i] == DELIMITER) {
329         offset = i;
330         break;
331       }
332     }
333     if(offset == -1) throw new IOException("Invalid regionName format");
334     byte [] tableName = new byte[offset];
335     System.arraycopy(regionName, 0, tableName, 0, offset);
336     offset = -1;
337     for (int i = regionName.length - 1; i > 0; i--) {
338       if(regionName[i] == DELIMITER) {
339         offset = i;
340         break;
341       }
342     }
343     if(offset == -1) throw new IOException("Invalid regionName format");
344     byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
345     if(offset != tableName.length + 1) {
346       startKey = new byte[offset - tableName.length - 1];
347       System.arraycopy(regionName, tableName.length + 1, startKey, 0,
348           offset - tableName.length - 1);
349     }
350     byte [] id = new byte[regionName.length - offset - 1];
351     System.arraycopy(regionName, offset + 1, id, 0,
352         regionName.length - offset - 1);
353     byte [][] elements = new byte[3][];
354     elements[0] = tableName;
355     elements[1] = startKey;
356     elements[2] = id;
357     return elements;
358   }
359 
360   /** @return the regionId */
361   public long getRegionId(){
362     return regionId;
363   }
364 
365   /**
366    * @return the regionName as an array of bytes.
367    * @see #getRegionNameAsString()
368    */
369   public byte [] getRegionName(){
370     return regionName;
371   }
372 
373   /**
374    * @return Region name as a String for use in logging, etc.
375    */
376   public String getRegionNameAsString() {
377     if (hasEncodedName(this.regionName)) {
378       // new format region names already have their encoded name.
379       return this.regionNameStr;
380     }
381 
382     // old format. regionNameStr doesn't have the region name.
383     //
384     //
385     return this.regionNameStr + "." + this.getEncodedName();
386   }
387 
388   /** @return the encoded region name */
389   public synchronized String getEncodedName() {
390     if (this.encodedName == NO_HASH) {
391       this.encodedName = encodeRegionName(this.regionName);
392     }
393     return this.encodedName;
394   }
395 
396   /** @return the startKey */
397   public byte [] getStartKey(){
398     return startKey;
399   }
400   
401   /** @return the endKey */
402   public byte [] getEndKey(){
403     return endKey;
404   }
405 
406   /**
407    * Returns true if the given inclusive range of rows is fully contained
408    * by this region. For example, if the region is foo,a,g and this is
409    * passed ["b","c"] or ["a","c"] it will return true, but if this is passed
410    * ["b","z"] it will return false.
411    * @throws IllegalArgumentException if the range passed is invalid (ie end < start)
412    */
413   public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) {
414     if (Bytes.compareTo(rangeStartKey, rangeEndKey) > 0) {
415       throw new IllegalArgumentException(
416       "Invalid range: " + Bytes.toStringBinary(rangeStartKey) +
417       " > " + Bytes.toStringBinary(rangeEndKey));
418     }
419 
420     boolean firstKeyInRange = Bytes.compareTo(rangeStartKey, startKey) >= 0;
421     boolean lastKeyInRange =
422       Bytes.compareTo(rangeEndKey, endKey) < 0 ||
423       Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY);
424     return firstKeyInRange && lastKeyInRange;
425   }
426   
427   /**
428    * Return true if the given row falls in this region.
429    */
430   public boolean containsRow(byte[] row) {
431     return Bytes.compareTo(row, startKey) >= 0 &&
432       (Bytes.compareTo(row, endKey) < 0 ||
433        Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY));
434   }
435 
436   /** @return the tableDesc */
437   public HTableDescriptor getTableDesc(){
438     return tableDesc;
439   }
440 
441   /**
442    * @param newDesc new table descriptor to use
443    */
444   public void setTableDesc(HTableDescriptor newDesc) {
445     this.tableDesc = newDesc;
446   }
447 
448   /** @return true if this is the root region */
449   public boolean isRootRegion() {
450     return this.tableDesc.isRootRegion();
451   }
452 
453   /** @return true if this is the meta table */
454   public boolean isMetaTable() {
455     return this.tableDesc.isMetaTable();
456   }
457 
458   /** @return true if this region is a meta region */
459   public boolean isMetaRegion() {
460     return this.tableDesc.isMetaRegion();
461   }
462 
463   /**
464    * @return True if has been split and has daughters.
465    */
466   public boolean isSplit() {
467     return this.split;
468   }
469 
470   /**
471    * @param split set split status
472    */
473   public void setSplit(boolean split) {
474     this.split = split;
475   }
476 
477   /**
478    * @return True if this region is offline.
479    */
480   public boolean isOffline() {
481     return this.offLine;
482   }
483 
484   /**
485    * @param offLine set online - offline status
486    */
487   public void setOffline(boolean offLine) {
488     this.offLine = offLine;
489   }
490 
491   /**
492    * @see java.lang.Object#toString()
493    */
494   @Override
495   public String toString() {
496     return "REGION => {" + HConstants.NAME + " => '" +
497       this.regionNameStr +
498       "', STARTKEY => '" +
499       Bytes.toStringBinary(this.startKey) + "', ENDKEY => '" +
500       Bytes.toStringBinary(this.endKey) +
501       "', ENCODED => " + getEncodedName() + "," +
502       (isOffline()? " OFFLINE => true,": "") +
503       (isSplit()? " SPLIT => true,": "") +
504       " TABLE => {" + this.tableDesc.toString() + "}";
505   }
506 
507   /**
508    * @see java.lang.Object#equals(java.lang.Object)
509    */
510   @Override
511   public boolean equals(Object o) {
512     if (this == o) {
513       return true;
514     }
515     if (o == null) {
516       return false;
517     }
518     if (!(o instanceof HRegionInfo)) {
519       return false;
520     }
521     return this.compareTo((HRegionInfo)o) == 0;
522   }
523 
524   /**
525    * @see java.lang.Object#hashCode()
526    */
527   @Override
528   public int hashCode() {
529     return this.hashCode;
530   }
531 
532   /** @return the object version number */
533   @Override
534   public byte getVersion() {
535     return VERSION;
536   }
537 
538   //
539   // Writable
540   //
541 
542   @Override
543   public void write(DataOutput out) throws IOException {
544     super.write(out);
545     Bytes.writeByteArray(out, endKey);
546     out.writeBoolean(offLine);
547     out.writeLong(regionId);
548     Bytes.writeByteArray(out, regionName);
549     out.writeBoolean(split);
550     Bytes.writeByteArray(out, startKey);
551     tableDesc.write(out);
552     out.writeInt(hashCode);
553   }
554 
555   @Override
556   public void readFields(DataInput in) throws IOException {
557     super.readFields(in);
558     this.endKey = Bytes.readByteArray(in);
559     this.offLine = in.readBoolean();
560     this.regionId = in.readLong();
561     this.regionName = Bytes.readByteArray(in);
562     this.regionNameStr = Bytes.toStringBinary(this.regionName);
563     this.split = in.readBoolean();
564     this.startKey = Bytes.readByteArray(in);
565     this.tableDesc.readFields(in);
566     this.hashCode = in.readInt();
567   }
568 
569   //
570   // Comparable
571   //
572 
573   public int compareTo(HRegionInfo o) {
574     if (o == null) {
575       return 1;
576     }
577 
578     // Are regions of same table?
579     int result = this.tableDesc.compareTo(o.tableDesc);
580     if (result != 0) {
581       return result;
582     }
583 
584     // Compare start keys.
585     result = Bytes.compareTo(this.startKey, o.startKey);
586     if (result != 0) {
587       return result;
588     }
589 
590     // Compare end keys.
591     return Bytes.compareTo(this.endKey, o.endKey);
592   }
593 
594   /**
595    * @return Comparator to use comparing {@link KeyValue}s.
596    */
597   public KVComparator getComparator() {
598     return isRootRegion()? KeyValue.ROOT_COMPARATOR: isMetaRegion()?
599       KeyValue.META_COMPARATOR: KeyValue.COMPARATOR;
600   }
601 }