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