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  package org.apache.hadoop.hbase.catalog;
19  
20  import java.io.IOException;
21  import java.io.InterruptedIOException;
22  import java.net.ConnectException;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  import org.apache.hadoop.hbase.DoNotRetryIOException;
30  import org.apache.hadoop.hbase.HConstants;
31  import org.apache.hadoop.hbase.HRegionInfo;
32  import org.apache.hadoop.hbase.NotAllMetaRegionsOnlineException;
33  import org.apache.hadoop.hbase.ServerName;
34  import org.apache.hadoop.hbase.client.Delete;
35  import org.apache.hadoop.hbase.client.HTable;
36  import org.apache.hadoop.hbase.client.Mutation;
37  import org.apache.hadoop.hbase.client.Put;
38  import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
39  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
40  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType;
41  import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos.MultiRowMutationService;
42  import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos.MutateRowsRequest;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
45  import org.apache.hadoop.hbase.util.Threads;
46  
47  import com.google.protobuf.ServiceException;
48  
49  /**
50   * Writes region and assignment information to <code>hbase:meta</code>.
51   * TODO: Put MetaReader and MetaEditor together; doesn't make sense having
52   * them distinct. see HBASE-3475.
53   */
54  @InterfaceAudience.Private
55  public class MetaEditor {
56    // TODO: Strip CatalogTracker from this class.  Its all over and in the end
57    // its only used to get its Configuration so we can get associated
58    // Connection.
59    private static final Log LOG = LogFactory.getLog(MetaEditor.class);
60  
61    /**
62     * Generates and returns a Put containing the region into for the catalog table
63     */
64    public static Put makePutFromRegionInfo(HRegionInfo regionInfo)
65    throws IOException {
66      Put put = new Put(regionInfo.getRegionName());
67      addRegionInfo(put, regionInfo);
68      return put;
69    }
70  
71    /**
72     * Generates and returns a Delete containing the region info for the catalog
73     * table
74     */
75    public static Delete makeDeleteFromRegionInfo(HRegionInfo regionInfo) {
76      if (regionInfo == null) {
77        throw new IllegalArgumentException("Can't make a delete for null region");
78      }
79      Delete delete = new Delete(regionInfo.getRegionName());
80      return delete;
81    }
82  
83    /**
84     * Adds split daughters to the Put
85     */
86    public static Put addDaughtersToPut(Put put, HRegionInfo splitA, HRegionInfo splitB) {
87      if (splitA != null) {
88        put.addImmutable(
89            HConstants.CATALOG_FAMILY, HConstants.SPLITA_QUALIFIER, splitA.toByteArray());
90      }
91      if (splitB != null) {
92        put.addImmutable(
93            HConstants.CATALOG_FAMILY, HConstants.SPLITB_QUALIFIER, splitB.toByteArray());
94      }
95      return put;
96    }
97  
98    /**
99     * Put the passed <code>p</code> to the <code>hbase:meta</code> table.
100    * @param ct CatalogTracker on whose back we will ride the edit.
101    * @param p Put to add to hbase:meta
102    * @throws IOException
103    */
104   static void putToMetaTable(final CatalogTracker ct, final Put p)
105   throws IOException {
106     put(MetaReader.getMetaHTable(ct), p);
107   }
108 
109   /**
110    * Put the passed <code>p</code> to a catalog table.
111    * @param ct CatalogTracker on whose back we will ride the edit.
112    * @param p Put to add
113    * @throws IOException
114    */
115   static void putToCatalogTable(final CatalogTracker ct, final Put p)
116   throws IOException {
117     put(MetaReader.getCatalogHTable(ct), p);
118   }
119 
120   /**
121    * @param t Table to use (will be closed when done).
122    * @param p
123    * @throws IOException
124    */
125   private static void put(final HTable t, final Put p) throws IOException {
126     try {
127       t.put(p);
128     } finally {
129       t.close();
130     }
131   }
132 
133   /**
134    * Put the passed <code>ps</code> to the <code>hbase:meta</code> table.
135    * @param ct CatalogTracker on whose back we will ride the edit.
136    * @param ps Put to add to hbase:meta
137    * @throws IOException
138    */
139   public static void putsToMetaTable(final CatalogTracker ct, final List<Put> ps)
140   throws IOException {
141     HTable t = MetaReader.getMetaHTable(ct);
142     try {
143       t.put(ps);
144     } finally {
145       t.close();
146     }
147   }
148 
149   /**
150    * Delete the passed <code>d</code> from the <code>hbase:meta</code> table.
151    * @param ct CatalogTracker on whose back we will ride the edit.
152    * @param d Delete to add to hbase:meta
153    * @throws IOException
154    */
155   static void deleteFromMetaTable(final CatalogTracker ct, final Delete d)
156       throws IOException {
157     List<Delete> dels = new ArrayList<Delete>(1);
158     dels.add(d);
159     deleteFromMetaTable(ct, dels);
160   }
161 
162   /**
163    * Delete the passed <code>deletes</code> from the <code>hbase:meta</code> table.
164    * @param ct CatalogTracker on whose back we will ride the edit.
165    * @param deletes Deletes to add to hbase:meta  This list should support #remove.
166    * @throws IOException
167    */
168   public static void deleteFromMetaTable(final CatalogTracker ct, final List<Delete> deletes)
169       throws IOException {
170     HTable t = MetaReader.getMetaHTable(ct);
171     try {
172       t.delete(deletes);
173     } finally {
174       t.close();
175     }
176   }
177 
178   /**
179    * Execute the passed <code>mutations</code> against <code>hbase:meta</code> table.
180    * @param ct CatalogTracker on whose back we will ride the edit.
181    * @param mutations Puts and Deletes to execute on hbase:meta
182    * @throws IOException
183    */
184   public static void mutateMetaTable(final CatalogTracker ct, final List<Mutation> mutations)
185       throws IOException {
186     HTable t = MetaReader.getMetaHTable(ct);
187     try {
188       t.batch(mutations);
189     } catch (InterruptedException e) {
190       InterruptedIOException ie = new InterruptedIOException(e.getMessage());
191       ie.initCause(e);
192       throw ie;
193     } finally {
194       t.close();
195     }
196   }
197 
198   /**
199    * Adds a hbase:meta row for the specified new region.
200    * @param regionInfo region information
201    * @throws IOException if problem connecting or updating meta
202    */
203   public static void addRegionToMeta(CatalogTracker catalogTracker,
204       HRegionInfo regionInfo)
205   throws IOException {
206     putToMetaTable(catalogTracker, makePutFromRegionInfo(regionInfo));
207     LOG.info("Added " + regionInfo.getRegionNameAsString());
208   }
209 
210   /**
211    * Adds a hbase:meta row for the specified new region to the given catalog table. The
212    * HTable is not flushed or closed.
213    * @param meta the HTable for META
214    * @param regionInfo region information
215    * @throws IOException if problem connecting or updating meta
216    */
217   public static void addRegionToMeta(HTable meta, HRegionInfo regionInfo) throws IOException {
218     addRegionToMeta(meta, regionInfo, null, null);
219   }
220 
221   /**
222    * Adds a (single) hbase:meta row for the specified new region and its daughters. Note that this does
223    * not add its daughter's as different rows, but adds information about the daughters
224    * in the same row as the parent. Use
225    * {@link #splitRegion(CatalogTracker, HRegionInfo, HRegionInfo, HRegionInfo, ServerName)}
226    * if you want to do that.
227    * @param meta the HTable for META
228    * @param regionInfo region information
229    * @param splitA first split daughter of the parent regionInfo
230    * @param splitB second split daughter of the parent regionInfo
231    * @throws IOException if problem connecting or updating meta
232    */
233   public static void addRegionToMeta(HTable meta, HRegionInfo regionInfo,
234       HRegionInfo splitA, HRegionInfo splitB) throws IOException {
235     Put put = makePutFromRegionInfo(regionInfo);
236     addDaughtersToPut(put, splitA, splitB);
237     meta.put(put);
238     if (LOG.isDebugEnabled()) {
239       LOG.debug("Added " + regionInfo.getRegionNameAsString());
240     }
241   }
242 
243   /**
244    * Adds a (single) hbase:meta row for the specified new region and its daughters. Note that this does
245    * not add its daughter's as different rows, but adds information about the daughters
246    * in the same row as the parent. Use
247    * {@link #splitRegion(CatalogTracker, HRegionInfo, HRegionInfo, HRegionInfo, ServerName)}
248    * if you want to do that.
249    * @param catalogTracker CatalogTracker on whose back we will ride the edit.
250    * @param regionInfo region information
251    * @param splitA first split daughter of the parent regionInfo
252    * @param splitB second split daughter of the parent regionInfo
253    * @throws IOException if problem connecting or updating meta
254    */
255   public static void addRegionToMeta(CatalogTracker catalogTracker, HRegionInfo regionInfo,
256       HRegionInfo splitA, HRegionInfo splitB) throws IOException {
257     HTable meta = MetaReader.getMetaHTable(catalogTracker);
258     try {
259       addRegionToMeta(meta, regionInfo, splitA, splitB);
260     } finally {
261       meta.close();
262     }
263   }
264 
265   /**
266    * Adds a hbase:meta row for each of the specified new regions.
267    * @param catalogTracker CatalogTracker
268    * @param regionInfos region information list
269    * @throws IOException if problem connecting or updating meta
270    */
271   public static void addRegionsToMeta(CatalogTracker catalogTracker,
272       List<HRegionInfo> regionInfos)
273   throws IOException {
274     List<Put> puts = new ArrayList<Put>();
275     for (HRegionInfo regionInfo : regionInfos) {
276       puts.add(makePutFromRegionInfo(regionInfo));
277     }
278     putsToMetaTable(catalogTracker, puts);
279     LOG.info("Added " + puts.size());
280   }
281 
282   /**
283    * Adds a daughter region entry to meta.
284    * @param regionInfo the region to put
285    * @param sn the location of the region
286    * @param openSeqNum the latest sequence number obtained when the region was open
287    */
288   public static void addDaughter(final CatalogTracker catalogTracker,
289       final HRegionInfo regionInfo, final ServerName sn, final long openSeqNum)
290   throws NotAllMetaRegionsOnlineException, IOException {
291     Put put = new Put(regionInfo.getRegionName());
292     addRegionInfo(put, regionInfo);
293     if (sn != null) {
294       addLocation(put, sn, openSeqNum);
295     }
296     putToMetaTable(catalogTracker, put);
297     LOG.info("Added daughter " + regionInfo.getEncodedName() +
298       (sn == null? ", serverName=null": ", serverName=" + sn.toString()));
299   }
300 
301   /**
302    * Merge the two regions into one in an atomic operation. Deletes the two
303    * merging regions in hbase:meta and adds the merged region with the information of
304    * two merging regions.
305    * @param catalogTracker the catalog tracker
306    * @param mergedRegion the merged region
307    * @param regionA
308    * @param regionB
309    * @param sn the location of the region
310    * @throws IOException
311    */
312   public static void mergeRegions(final CatalogTracker catalogTracker,
313       HRegionInfo mergedRegion, HRegionInfo regionA, HRegionInfo regionB,
314       ServerName sn) throws IOException {
315     HTable meta = MetaReader.getMetaHTable(catalogTracker);
316     try {
317       HRegionInfo copyOfMerged = new HRegionInfo(mergedRegion);
318 
319       // Put for parent
320       Put putOfMerged = makePutFromRegionInfo(copyOfMerged);
321       putOfMerged.addImmutable(HConstants.CATALOG_FAMILY, HConstants.MERGEA_QUALIFIER,
322           regionA.toByteArray());
323       putOfMerged.addImmutable(HConstants.CATALOG_FAMILY, HConstants.MERGEB_QUALIFIER,
324           regionB.toByteArray());
325 
326       // Deletes for merging regions
327       Delete deleteA = makeDeleteFromRegionInfo(regionA);
328       Delete deleteB = makeDeleteFromRegionInfo(regionB);
329 
330       // The merged is a new region, openSeqNum = 1 is fine.
331       addLocation(putOfMerged, sn, 1);
332 
333       byte[] tableRow = Bytes.toBytes(mergedRegion.getRegionNameAsString()
334           + HConstants.DELIMITER);
335       multiMutate(meta, tableRow, putOfMerged, deleteA, deleteB);
336     } finally {
337       meta.close();
338     }
339   }
340 
341   /**
342    * Splits the region into two in an atomic operation. Offlines the parent
343    * region with the information that it is split into two, and also adds
344    * the daughter regions. Does not add the location information to the daughter
345    * regions since they are not open yet.
346    * @param catalogTracker the catalog tracker
347    * @param parent the parent region which is split
348    * @param splitA Split daughter region A
349    * @param splitB Split daughter region A
350    * @param sn the location of the region
351    */
352   public static void splitRegion(final CatalogTracker catalogTracker,
353       HRegionInfo parent, HRegionInfo splitA, HRegionInfo splitB,
354       ServerName sn) throws IOException {
355     HTable meta = MetaReader.getMetaHTable(catalogTracker);
356     try {
357       HRegionInfo copyOfParent = new HRegionInfo(parent);
358       copyOfParent.setOffline(true);
359       copyOfParent.setSplit(true);
360 
361       //Put for parent
362       Put putParent = makePutFromRegionInfo(copyOfParent);
363       addDaughtersToPut(putParent, splitA, splitB);
364 
365       //Puts for daughters
366       Put putA = makePutFromRegionInfo(splitA);
367       Put putB = makePutFromRegionInfo(splitB);
368 
369       addLocation(putA, sn, 1); //these are new regions, openSeqNum = 1 is fine.
370       addLocation(putB, sn, 1);
371 
372       byte[] tableRow = Bytes.toBytes(parent.getRegionNameAsString() + HConstants.DELIMITER);
373       multiMutate(meta, tableRow, putParent, putA, putB);
374     } finally {
375       meta.close();
376     }
377   }
378 
379   /**
380    * Performs an atomic multi-Mutate operation against the given table.
381    */
382   private static void multiMutate(HTable table, byte[] row, Mutation... mutations) throws IOException {
383     CoprocessorRpcChannel channel = table.coprocessorService(row);
384     MutateRowsRequest.Builder mmrBuilder = MutateRowsRequest.newBuilder();
385     for (Mutation mutation : mutations) {
386       if (mutation instanceof Put) {
387         mmrBuilder.addMutationRequest(ProtobufUtil.toMutation(MutationType.PUT, mutation));
388       } else if (mutation instanceof Delete) {
389         mmrBuilder.addMutationRequest(ProtobufUtil.toMutation(MutationType.DELETE, mutation));
390       } else {
391         throw new DoNotRetryIOException("multi in MetaEditor doesn't support "
392             + mutation.getClass().getName());
393       }
394     }
395 
396     MultiRowMutationService.BlockingInterface service =
397         MultiRowMutationService.newBlockingStub(channel);
398     try {
399       service.mutateRows(null, mmrBuilder.build());
400     } catch (ServiceException ex) {
401       ProtobufUtil.toIOException(ex);
402     }
403   }
404 
405 
406   /**
407    * Updates the location of the specified hbase:meta region in ROOT to be the
408    * specified server hostname and startcode.
409    * <p>
410    * Uses passed catalog tracker to get a connection to the server hosting
411    * ROOT and makes edits to that region.
412    *
413    * @param catalogTracker catalog tracker
414    * @param regionInfo region to update location of
415    * @param sn Server name
416    * @param openSeqNum the latest sequence number obtained when the region was open
417    * @throws IOException
418    * @throws ConnectException Usually because the regionserver carrying hbase:meta
419    * is down.
420    * @throws NullPointerException Because no -ROOT- server connection
421    */
422   public static void updateMetaLocation(CatalogTracker catalogTracker,
423       HRegionInfo regionInfo, ServerName sn, long openSeqNum)
424   throws IOException, ConnectException {
425     updateLocation(catalogTracker, regionInfo, sn, openSeqNum);
426   }
427 
428   /**
429    * Updates the location of the specified region in hbase:meta to be the specified
430    * server hostname and startcode.
431    * <p>
432    * Uses passed catalog tracker to get a connection to the server hosting
433    * hbase:meta and makes edits to that region.
434    *
435    * @param catalogTracker catalog tracker
436    * @param regionInfo region to update location of
437    * @param sn Server name
438    * @throws IOException
439    */
440   public static void updateRegionLocation(CatalogTracker catalogTracker,
441       HRegionInfo regionInfo, ServerName sn, long updateSeqNum)
442   throws IOException {
443     updateLocation(catalogTracker, regionInfo, sn, updateSeqNum);
444   }
445 
446   /**
447    * Updates the location of the specified region to be the specified server.
448    * <p>
449    * Connects to the specified server which should be hosting the specified
450    * catalog region name to perform the edit.
451    *
452    * @param catalogTracker
453    * @param regionInfo region to update location of
454    * @param sn Server name
455    * @param openSeqNum the latest sequence number obtained when the region was open
456    * @throws IOException In particular could throw {@link java.net.ConnectException}
457    * if the server is down on other end.
458    */
459   private static void updateLocation(final CatalogTracker catalogTracker,
460       HRegionInfo regionInfo, ServerName sn, long openSeqNum)
461   throws IOException {
462     Put put = new Put(regionInfo.getRegionName());
463     addLocation(put, sn, openSeqNum);
464     putToCatalogTable(catalogTracker, put);
465     LOG.info("Updated row " + regionInfo.getRegionNameAsString() +
466       " with server=" + sn);
467   }
468 
469   /**
470    * Deletes the specified region from META.
471    * @param catalogTracker
472    * @param regionInfo region to be deleted from META
473    * @throws IOException
474    */
475   public static void deleteRegion(CatalogTracker catalogTracker,
476       HRegionInfo regionInfo)
477   throws IOException {
478     Delete delete = new Delete(regionInfo.getRegionName());
479     deleteFromMetaTable(catalogTracker, delete);
480     LOG.info("Deleted " + regionInfo.getRegionNameAsString());
481   }
482 
483   /**
484    * Deletes the specified regions from META.
485    * @param catalogTracker
486    * @param regionsInfo list of regions to be deleted from META
487    * @throws IOException
488    */
489   public static void deleteRegions(CatalogTracker catalogTracker,
490       List<HRegionInfo> regionsInfo) throws IOException {
491     List<Delete> deletes = new ArrayList<Delete>(regionsInfo.size());
492     for (HRegionInfo hri: regionsInfo) {
493       deletes.add(new Delete(hri.getRegionName()));
494     }
495     deleteFromMetaTable(catalogTracker, deletes);
496     LOG.info("Deleted " + regionsInfo);
497   }
498 
499   /**
500    * Adds and Removes the specified regions from hbase:meta
501    * @param catalogTracker
502    * @param regionsToRemove list of regions to be deleted from META
503    * @param regionsToAdd list of regions to be added to META
504    * @throws IOException
505    */
506   public static void mutateRegions(CatalogTracker catalogTracker,
507       final List<HRegionInfo> regionsToRemove, final List<HRegionInfo> regionsToAdd)
508       throws IOException {
509     List<Mutation> mutation = new ArrayList<Mutation>();
510     if (regionsToRemove != null) {
511       for (HRegionInfo hri: regionsToRemove) {
512         mutation.add(new Delete(hri.getRegionName()));
513       }
514     }
515     if (regionsToAdd != null) {
516       for (HRegionInfo hri: regionsToAdd) {
517         mutation.add(makePutFromRegionInfo(hri));
518       }
519     }
520     mutateMetaTable(catalogTracker, mutation);
521     if (regionsToRemove != null && regionsToRemove.size() > 0) {
522       LOG.debug("Deleted " + regionsToRemove);
523     }
524     if (regionsToAdd != null && regionsToAdd.size() > 0) {
525       LOG.debug("Added " + regionsToAdd);
526     }
527   }
528 
529   /**
530    * Overwrites the specified regions from hbase:meta
531    * @param catalogTracker
532    * @param regionInfos list of regions to be added to META
533    * @throws IOException
534    */
535   public static void overwriteRegions(CatalogTracker catalogTracker,
536       List<HRegionInfo> regionInfos) throws IOException {
537     deleteRegions(catalogTracker, regionInfos);
538     // Why sleep? This is the easiest way to ensure that the previous deletes does not
539     // eclipse the following puts, that might happen in the same ts from the server.
540     // See HBASE-9906, and HBASE-9879. Once either HBASE-9879, HBASE-8770 is fixed,
541     // or HBASE-9905 is fixed and meta uses seqIds, we do not need the sleep.
542     Threads.sleep(20);
543     addRegionsToMeta(catalogTracker, regionInfos);
544     LOG.info("Overwritten " + regionInfos);
545   }
546 
547   /**
548    * Deletes merge qualifiers for the specified merged region.
549    * @param catalogTracker
550    * @param mergedRegion
551    * @throws IOException
552    */
553   public static void deleteMergeQualifiers(CatalogTracker catalogTracker,
554       final HRegionInfo mergedRegion) throws IOException {
555     Delete delete = new Delete(mergedRegion.getRegionName());
556     delete.deleteColumns(HConstants.CATALOG_FAMILY, HConstants.MERGEA_QUALIFIER);
557     delete.deleteColumns(HConstants.CATALOG_FAMILY, HConstants.MERGEB_QUALIFIER);
558     deleteFromMetaTable(catalogTracker, delete);
559     LOG.info("Deleted references in merged region "
560         + mergedRegion.getRegionNameAsString() + ", qualifier="
561         + Bytes.toStringBinary(HConstants.MERGEA_QUALIFIER) + " and qualifier="
562         + Bytes.toStringBinary(HConstants.MERGEB_QUALIFIER));
563   }
564 
565   private static Put addRegionInfo(final Put p, final HRegionInfo hri)
566   throws IOException {
567     p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
568         hri.toByteArray());
569     return p;
570   }
571 
572   private static Put addLocation(final Put p, final ServerName sn, long openSeqNum) {
573     // using regionserver's local time as the timestamp of Put.
574     // See: HBASE-11536
575     long now = EnvironmentEdgeManager.currentTimeMillis();
576     p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER, now,
577       Bytes.toBytes(sn.getHostAndPort()));
578     p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.STARTCODE_QUALIFIER, now,
579       Bytes.toBytes(sn.getStartcode()));
580     p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.SEQNUM_QUALIFIER, now,
581         Bytes.toBytes(openSeqNum));
582     return p;
583   }
584 }