View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.master.handler;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.concurrent.ExecutorService;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.classification.InterfaceAudience;
29  import org.apache.hadoop.hbase.TableName;
30  import org.apache.hadoop.hbase.HRegionInfo;
31  import org.apache.hadoop.hbase.Server;
32  import org.apache.hadoop.hbase.ServerName;
33  import org.apache.hadoop.hbase.TableNotDisabledException;
34  import org.apache.hadoop.hbase.TableNotFoundException;
35  import org.apache.hadoop.hbase.catalog.CatalogTracker;
36  import org.apache.hadoop.hbase.catalog.MetaReader;
37  import org.apache.hadoop.hbase.executor.EventHandler;
38  import org.apache.hadoop.hbase.executor.EventType;
39  import org.apache.hadoop.hbase.master.AssignmentManager;
40  import org.apache.hadoop.hbase.master.BulkAssigner;
41  import org.apache.hadoop.hbase.master.HMaster;
42  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
43  import org.apache.hadoop.hbase.master.RegionPlan;
44  import org.apache.hadoop.hbase.master.RegionStates;
45  import org.apache.hadoop.hbase.master.ServerManager;
46  import org.apache.hadoop.hbase.master.TableLockManager;
47  import org.apache.hadoop.hbase.master.TableLockManager.TableLock;
48  import org.apache.hadoop.hbase.util.Pair;
49  import org.apache.zookeeper.KeeperException;
50  import org.cloudera.htrace.Trace;
51  
52  /**
53   * Handler to run enable of a table.
54   */
55  @InterfaceAudience.Private
56  public class EnableTableHandler extends EventHandler {
57    private static final Log LOG = LogFactory.getLog(EnableTableHandler.class);
58    private final TableName tableName;
59    private final AssignmentManager assignmentManager;
60    private final TableLockManager tableLockManager;
61    private final CatalogTracker catalogTracker;
62    private boolean retainAssignment = false;
63    private TableLock tableLock;
64  
65    public EnableTableHandler(Server server, TableName tableName,
66        CatalogTracker catalogTracker, AssignmentManager assignmentManager,
67        TableLockManager tableLockManager, boolean skipTableStateCheck) {
68      super(server, EventType.C_M_ENABLE_TABLE);
69      this.tableName = tableName;
70      this.catalogTracker = catalogTracker;
71      this.assignmentManager = assignmentManager;
72      this.tableLockManager = tableLockManager;
73      this.retainAssignment = skipTableStateCheck;
74    }
75  
76    public EnableTableHandler prepare()
77        throws TableNotFoundException, TableNotDisabledException, IOException {
78      //acquire the table write lock, blocking
79      this.tableLock = this.tableLockManager.writeLock(tableName,
80          EventType.C_M_ENABLE_TABLE.toString());
81      this.tableLock.acquire();
82  
83      boolean success = false;
84      try {
85        // Check if table exists
86        if (!MetaReader.tableExists(catalogTracker, tableName)) {
87          // retainAssignment is true only during recovery.  In normal case it is false
88          if (!this.retainAssignment) {
89            throw new TableNotFoundException(tableName);
90          } 
91          try {
92            this.assignmentManager.getZKTable().removeEnablingTable(tableName, true);
93          } catch (KeeperException e) {
94            // TODO : Use HBCK to clear such nodes
95            LOG.warn("Failed to delete the ENABLING node for the table " + tableName
96                + ".  The table will remain unusable. Run HBCK to manually fix the problem.");
97          }
98        }
99  
100       // There could be multiple client requests trying to disable or enable
101       // the table at the same time. Ensure only the first request is honored
102       // After that, no other requests can be accepted until the table reaches
103       // DISABLED or ENABLED.
104       if (!retainAssignment) {
105         try {
106           if (!this.assignmentManager.getZKTable().checkDisabledAndSetEnablingTable
107             (this.tableName)) {
108             LOG.info("Table " + tableName + " isn't disabled; skipping enable");
109             throw new TableNotDisabledException(this.tableName);
110           }
111         } catch (KeeperException e) {
112           throw new IOException("Unable to ensure that the table will be" +
113             " enabling because of a ZooKeeper issue", e);
114         }
115       }
116       success = true;
117     } finally {
118       if (!success) {
119         releaseTableLock();
120       }
121     }
122     return this;
123   }
124 
125   @Override
126   public String toString() {
127     String name = "UnknownServerName";
128     if(server != null && server.getServerName() != null) {
129       name = server.getServerName().toString();
130     }
131     return getClass().getSimpleName() + "-" + name + "-" + getSeqid() + "-" +
132         tableName;
133   }
134 
135   @Override
136   public void process() {
137     try {
138       LOG.info("Attempting to enable the table " + this.tableName);
139       MasterCoprocessorHost cpHost = ((HMaster) this.server)
140           .getCoprocessorHost();
141       if (cpHost != null) {
142         cpHost.preEnableTableHandler(this.tableName);
143       }
144       handleEnableTable();
145       if (cpHost != null) {
146         cpHost.postEnableTableHandler(this.tableName);
147       }
148     } catch (IOException e) {
149       LOG.error("Error trying to enable the table " + this.tableName, e);
150     } catch (KeeperException e) {
151       LOG.error("Error trying to enable the table " + this.tableName, e);
152     } catch (InterruptedException e) {
153       LOG.error("Error trying to enable the table " + this.tableName, e);
154     } finally {
155       releaseTableLock();
156     }
157   }
158 
159   private void releaseTableLock() {
160     if (this.tableLock != null) {
161       try {
162         this.tableLock.release();
163       } catch (IOException ex) {
164         LOG.warn("Could not release the table lock", ex);
165       }
166     }
167   }
168 
169   private void handleEnableTable() throws IOException, KeeperException, InterruptedException {
170     // I could check table is disabling and if so, not enable but require
171     // that user first finish disabling but that might be obnoxious.
172 
173     // Set table enabling flag up in zk.
174     this.assignmentManager.getZKTable().setEnablingTable(this.tableName);
175     boolean done = false;
176     // Get the regions of this table. We're done when all listed
177     // tables are onlined.
178     List<Pair<HRegionInfo, ServerName>> tableRegionsAndLocations = MetaReader
179         .getTableRegionsAndLocations(this.catalogTracker, tableName, true);
180     int countOfRegionsInTable = tableRegionsAndLocations.size();
181     List<HRegionInfo> regions = regionsToAssignWithServerName(tableRegionsAndLocations);
182     int regionsCount = regions.size();
183     if (regionsCount == 0) {
184       done = true;
185     }
186     LOG.info("Table '" + this.tableName + "' has " + countOfRegionsInTable
187       + " regions, of which " + regionsCount + " are offline.");
188     BulkEnabler bd = new BulkEnabler(this.server, regions, countOfRegionsInTable,
189         this.retainAssignment);
190     try {
191       if (bd.bulkAssign()) {
192         done = true;
193       }
194     } catch (InterruptedException e) {
195       LOG.warn("Enable operation was interrupted when enabling table '"
196         + this.tableName + "'");
197       // Preserve the interrupt.
198       Thread.currentThread().interrupt();
199     }
200     if (done) {
201       // Flip the table to enabled.
202       this.assignmentManager.getZKTable().setEnabledTable(
203         this.tableName);
204       LOG.info("Table '" + this.tableName
205       + "' was successfully enabled. Status: done=" + done);
206     } else {
207       LOG.warn("Table '" + this.tableName
208       + "' wasn't successfully enabled. Status: done=" + done);
209     }
210   }
211 
212   /**
213    * @param regionsInMeta
214    * @return List of regions neither in transition nor assigned.
215    * @throws IOException
216    */
217   private List<HRegionInfo> regionsToAssignWithServerName(
218       final List<Pair<HRegionInfo, ServerName>> regionsInMeta) throws IOException {
219     ServerManager serverManager = ((HMaster) this.server).getServerManager();
220     List<HRegionInfo> regions = new ArrayList<HRegionInfo>();
221     RegionStates regionStates = this.assignmentManager.getRegionStates();
222     for (Pair<HRegionInfo, ServerName> regionLocation : regionsInMeta) {
223       HRegionInfo hri = regionLocation.getFirst();
224       ServerName sn = regionLocation.getSecond();
225       if (!regionStates.isRegionInTransition(hri) && !regionStates.isRegionAssigned(hri)) {
226         if (this.retainAssignment && sn != null && serverManager.isServerOnline(sn)) {
227           this.assignmentManager.addPlan(hri.getEncodedName(), new RegionPlan(hri, null, sn));
228         }
229         regions.add(hri);
230       } else {
231         if (LOG.isDebugEnabled()) {
232           LOG.debug("Skipping assign for the region " + hri + " during enable table "
233               + hri.getTableName() + " because its already in tranition or assigned.");
234         }
235       }
236     }
237     return regions;
238   }
239 
240   /**
241    * Run bulk enable.
242    */
243   class BulkEnabler extends BulkAssigner {
244     private final List<HRegionInfo> regions;
245     // Count of regions in table at time this assign was launched.
246     private final int countOfRegionsInTable;
247     private final boolean retainAssignment;
248 
249     BulkEnabler(final Server server, final List<HRegionInfo> regions,
250         final int countOfRegionsInTable, boolean retainAssignment) {
251       super(server);
252       this.regions = regions;
253       this.countOfRegionsInTable = countOfRegionsInTable;
254       this.retainAssignment = retainAssignment;
255     }
256 
257     @Override
258     protected void populatePool(ExecutorService pool) throws IOException {
259       boolean roundRobinAssignment = this.server.getConfiguration().getBoolean(
260           "hbase.master.enabletable.roundrobin", false);
261 
262       // In case of masterRestart always go with single assign.  Going thro
263       // roundRobinAssignment will use bulkassign which may lead to double assignment.
264       if (retainAssignment || !roundRobinAssignment) {
265         for (HRegionInfo region : regions) {
266           if (assignmentManager.getRegionStates()
267               .isRegionInTransition(region)) {
268             continue;
269           }
270           final HRegionInfo hri = region;
271           pool.execute(Trace.wrap("BulkEnabler.populatePool",new Runnable() {
272             public void run() {
273               assignmentManager.assign(hri, true);
274             }
275           }));
276         }
277       } else {
278         try {
279           assignmentManager.assign(regions);
280         } catch (InterruptedException e) {
281           LOG.warn("Assignment was interrupted");
282           Thread.currentThread().interrupt();
283         }
284       }
285     }
286 
287     @Override
288     protected boolean waitUntilDone(long timeout)
289     throws InterruptedException {
290       long startTime = System.currentTimeMillis();
291       long remaining = timeout;
292       List<HRegionInfo> regions = null;
293       int lastNumberOfRegions = 0;
294       while (!server.isStopped() && remaining > 0) {
295         Thread.sleep(waitingTimeForEvents);
296         regions = assignmentManager.getRegionStates()
297           .getRegionsOfTable(tableName);
298         if (isDone(regions)) break;
299 
300         // Punt on the timeout as long we make progress
301         if (regions.size() > lastNumberOfRegions) {
302           lastNumberOfRegions = regions.size();
303           timeout += waitingTimeForEvents;
304         }
305         remaining = timeout - (System.currentTimeMillis() - startTime);
306       }
307       return isDone(regions);
308     }
309 
310     private boolean isDone(final List<HRegionInfo> regions) {
311       return regions != null && regions.size() >= this.countOfRegionsInTable;
312     }
313   }
314 }