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.HRegionInfo;
30  import org.apache.hadoop.hbase.Server;
31  import org.apache.hadoop.hbase.ServerName;
32  import org.apache.hadoop.hbase.exceptions.TableNotDisabledException;
33  import org.apache.hadoop.hbase.exceptions.TableNotFoundException;
34  import org.apache.hadoop.hbase.catalog.CatalogTracker;
35  import org.apache.hadoop.hbase.catalog.MetaReader;
36  import org.apache.hadoop.hbase.executor.EventHandler;
37  import org.apache.hadoop.hbase.executor.EventType;
38  import org.apache.hadoop.hbase.master.AssignmentManager;
39  import org.apache.hadoop.hbase.master.BulkAssigner;
40  import org.apache.hadoop.hbase.master.HMaster;
41  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
42  import org.apache.hadoop.hbase.master.RegionPlan;
43  import org.apache.hadoop.hbase.master.RegionStates;
44  import org.apache.hadoop.hbase.master.ServerManager;
45  import org.apache.hadoop.hbase.master.TableLockManager;
46  import org.apache.hadoop.hbase.master.TableLockManager.TableLock;
47  import org.apache.hadoop.hbase.util.Bytes;
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 byte [] tableName;
59    private final String tableNameStr;
60    private final AssignmentManager assignmentManager;
61    private final TableLockManager tableLockManager;
62    private final CatalogTracker catalogTracker;
63    private boolean retainAssignment = false;
64    private TableLock tableLock;
65  
66    public EnableTableHandler(Server server, byte [] tableName,
67        CatalogTracker catalogTracker, AssignmentManager assignmentManager,
68        TableLockManager tableLockManager, boolean skipTableStateCheck) {
69      super(server, EventType.C_M_ENABLE_TABLE);
70      this.tableName = tableName;
71      this.tableNameStr = Bytes.toString(tableName);
72      this.catalogTracker = catalogTracker;
73      this.assignmentManager = assignmentManager;
74      this.tableLockManager = tableLockManager;
75      this.retainAssignment = skipTableStateCheck;
76    }
77  
78    public EnableTableHandler prepare()
79        throws TableNotFoundException, TableNotDisabledException, IOException {
80      //acquire the table write lock, blocking
81      this.tableLock = this.tableLockManager.writeLock(tableName,
82          EventType.C_M_ENABLE_TABLE.toString());
83      this.tableLock.acquire();
84  
85      boolean success = false;
86      try {
87        // Check if table exists
88        if (!MetaReader.tableExists(catalogTracker, this.tableNameStr)) {
89          throw new TableNotFoundException(Bytes.toString(tableName));
90        }
91  
92        // There could be multiple client requests trying to disable or enable
93        // the table at the same time. Ensure only the first request is honored
94        // After that, no other requests can be accepted until the table reaches
95        // DISABLED or ENABLED.
96        if (!retainAssignment) {
97          try {
98            if (!this.assignmentManager.getZKTable().checkDisabledAndSetEnablingTable
99              (this.tableNameStr)) {
100             LOG.info("Table " + tableNameStr + " isn't disabled; skipping enable");
101             throw new TableNotDisabledException(this.tableNameStr);
102           }
103         } catch (KeeperException e) {
104           throw new IOException("Unable to ensure that the table will be" +
105             " enabling because of a ZooKeeper issue", e);
106         }
107       }
108       success = true;
109     } finally {
110       if (!success) {
111         releaseTableLock();
112       }
113     }
114     return this;
115   }
116 
117   @Override
118   public String toString() {
119     String name = "UnknownServerName";
120     if(server != null && server.getServerName() != null) {
121       name = server.getServerName().toString();
122     }
123     return getClass().getSimpleName() + "-" + name + "-" + getSeqid() + "-" +
124       tableNameStr;
125   }
126 
127   @Override
128   public void process() {
129     try {
130       LOG.info("Attempting to enable the table " + this.tableNameStr);
131       MasterCoprocessorHost cpHost = ((HMaster) this.server)
132           .getCoprocessorHost();
133       if (cpHost != null) {
134         cpHost.preEnableTableHandler(this.tableName);
135       }
136       handleEnableTable();
137       if (cpHost != null) {
138         cpHost.postEnableTableHandler(this.tableName);
139       }
140     } catch (IOException e) {
141       LOG.error("Error trying to enable the table " + this.tableNameStr, e);
142     } catch (KeeperException e) {
143       LOG.error("Error trying to enable the table " + this.tableNameStr, e);
144     } catch (InterruptedException e) {
145       LOG.error("Error trying to enable the table " + this.tableNameStr, e);
146     } finally {
147       releaseTableLock();
148     }
149   }
150 
151   private void releaseTableLock() {
152     if (this.tableLock != null) {
153       try {
154         this.tableLock.release();
155       } catch (IOException ex) {
156         LOG.warn("Could not release the table lock", ex);
157       }
158     }
159   }
160 
161   private void handleEnableTable() throws IOException, KeeperException, InterruptedException {
162     // I could check table is disabling and if so, not enable but require
163     // that user first finish disabling but that might be obnoxious.
164 
165     // Set table enabling flag up in zk.
166     this.assignmentManager.getZKTable().setEnablingTable(this.tableNameStr);
167     boolean done = false;
168     // Get the regions of this table. We're done when all listed
169     // tables are onlined.
170     List<Pair<HRegionInfo, ServerName>> tableRegionsAndLocations = MetaReader
171         .getTableRegionsAndLocations(this.catalogTracker, tableName, true);
172     int countOfRegionsInTable = tableRegionsAndLocations.size();
173     List<HRegionInfo> regions = regionsToAssignWithServerName(tableRegionsAndLocations);
174     int regionsCount = regions.size();
175     if (regionsCount == 0) {
176       done = true;
177     }
178     LOG.info("Table '" + this.tableNameStr + "' has " + countOfRegionsInTable
179       + " regions, of which " + regionsCount + " are offline.");
180     BulkEnabler bd = new BulkEnabler(this.server, regions, countOfRegionsInTable,
181         this.retainAssignment);
182     try {
183       if (bd.bulkAssign()) {
184         done = true;
185       }
186     } catch (InterruptedException e) {
187       LOG.warn("Enable operation was interrupted when enabling table '"
188         + this.tableNameStr + "'");
189       // Preserve the interrupt.
190       Thread.currentThread().interrupt();
191     }
192     if (done) {
193       // Flip the table to enabled.
194       this.assignmentManager.getZKTable().setEnabledTable(
195         this.tableNameStr);
196       LOG.info("Table '" + this.tableNameStr
197       + "' was successfully enabled. Status: done=" + done);
198     } else {
199       LOG.warn("Table '" + this.tableNameStr
200       + "' wasn't successfully enabled. Status: done=" + done);
201     }
202   }
203 
204   /**
205    * @param regionsInMeta
206    * @return List of regions neither in transition nor assigned.
207    * @throws IOException
208    */
209   private List<HRegionInfo> regionsToAssignWithServerName(
210       final List<Pair<HRegionInfo, ServerName>> regionsInMeta) throws IOException {
211     ServerManager serverManager = ((HMaster) this.server).getServerManager();
212     List<HRegionInfo> regions = new ArrayList<HRegionInfo>();
213     RegionStates regionStates = this.assignmentManager.getRegionStates();
214     for (Pair<HRegionInfo, ServerName> regionLocation : regionsInMeta) {
215       HRegionInfo hri = regionLocation.getFirst();
216       ServerName sn = regionLocation.getSecond();
217       if (!regionStates.isRegionInTransition(hri) && !regionStates.isRegionAssigned(hri)) {
218         if (this.retainAssignment && sn != null && serverManager.isServerOnline(sn)) {
219           this.assignmentManager.addPlan(hri.getEncodedName(), new RegionPlan(hri, null, sn));
220         }
221         regions.add(hri);
222       } else {
223         if (LOG.isDebugEnabled()) {
224           LOG.debug("Skipping assign for the region " + hri + " during enable table "
225               + hri.getTableNameAsString() + " because its already in tranition or assigned.");
226         }
227       }
228     }
229     return regions;
230   }
231 
232   /**
233    * Run bulk enable.
234    */
235   class BulkEnabler extends BulkAssigner {
236     private final List<HRegionInfo> regions;
237     // Count of regions in table at time this assign was launched.
238     private final int countOfRegionsInTable;
239     private final boolean retainAssignment;
240 
241     BulkEnabler(final Server server, final List<HRegionInfo> regions,
242         final int countOfRegionsInTable, boolean retainAssignment) {
243       super(server);
244       this.regions = regions;
245       this.countOfRegionsInTable = countOfRegionsInTable;
246       this.retainAssignment = retainAssignment;
247     }
248 
249     @Override
250     protected void populatePool(ExecutorService pool) throws IOException {
251       boolean roundRobinAssignment = this.server.getConfiguration().getBoolean(
252           "hbase.master.enabletable.roundrobin", false);
253 
254       // In case of masterRestart always go with single assign.  Going thro
255       // roundRobinAssignment will use bulkassign which may lead to double assignment.
256       if (retainAssignment || !roundRobinAssignment) {
257         for (HRegionInfo region : regions) {
258           if (assignmentManager.getRegionStates()
259               .isRegionInTransition(region)) {
260             continue;
261           }
262           final HRegionInfo hri = region;
263           pool.execute(Trace.wrap(new Runnable() {
264             public void run() {
265               assignmentManager.assign(hri, true);
266             }
267           }));
268         }
269       } else {
270         try {
271           assignmentManager.assign(regions);
272         } catch (InterruptedException e) {
273           LOG.warn("Assignment was interrupted");
274           Thread.currentThread().interrupt();
275         }
276       }
277     }
278 
279     @Override
280     protected boolean waitUntilDone(long timeout)
281     throws InterruptedException {
282       long startTime = System.currentTimeMillis();
283       long remaining = timeout;
284       List<HRegionInfo> regions = null;
285       int lastNumberOfRegions = 0;
286       while (!server.isStopped() && remaining > 0) {
287         Thread.sleep(waitingTimeForEvents);
288         regions = assignmentManager.getRegionStates()
289           .getRegionsOfTable(tableName);
290         if (isDone(regions)) break;
291 
292         // Punt on the timeout as long we make progress
293         if (regions.size() > lastNumberOfRegions) {
294           lastNumberOfRegions = regions.size();
295           timeout += waitingTimeForEvents;
296         }
297         remaining = timeout - (System.currentTimeMillis() - startTime);
298       }
299       return isDone(regions);
300     }
301 
302     private boolean isDone(final List<HRegionInfo> regions) {
303       return regions != null && regions.size() >= this.countOfRegionsInTable;
304     }
305   }
306 }