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.HashMap;
23  import java.util.List;
24  import java.util.Map;
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.CoordinatedStateException;
30  import org.apache.hadoop.hbase.TableName;
31  import org.apache.hadoop.hbase.HRegionInfo;
32  import org.apache.hadoop.hbase.Server;
33  import org.apache.hadoop.hbase.ServerName;
34  import org.apache.hadoop.hbase.TableNotDisabledException;
35  import org.apache.hadoop.hbase.TableNotFoundException;
36  import org.apache.hadoop.hbase.catalog.CatalogTracker;
37  import org.apache.hadoop.hbase.catalog.MetaReader;
38  import org.apache.hadoop.hbase.executor.EventHandler;
39  import org.apache.hadoop.hbase.executor.EventType;
40  import org.apache.hadoop.hbase.master.AssignmentManager;
41  import org.apache.hadoop.hbase.master.BulkAssigner;
42  import org.apache.hadoop.hbase.master.GeneralBulkAssigner;
43  import org.apache.hadoop.hbase.master.HMaster;
44  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
45  import org.apache.hadoop.hbase.master.RegionStates;
46  import org.apache.hadoop.hbase.master.ServerManager;
47  import org.apache.hadoop.hbase.master.TableLockManager;
48  import org.apache.hadoop.hbase.master.TableLockManager.TableLock;
49  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
50  import org.apache.hadoop.hbase.util.Pair;
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 skipTableStateCheck = 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.skipTableStateCheck = 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.skipTableStateCheck) {
89            throw new TableNotFoundException(tableName);
90          } 
91          try {
92            this.assignmentManager.getTableStateManager().checkAndRemoveTableState(tableName,
93              ZooKeeperProtos.Table.State.ENABLING, true);
94            throw new TableNotFoundException(tableName);
95          } catch (CoordinatedStateException e) {
96            // TODO : Use HBCK to clear such nodes
97            LOG.warn("Failed to delete the ENABLING node for the table " + tableName
98                + ".  The table will remain unusable. Run HBCK to manually fix the problem.");
99          }
100       }
101 
102       // There could be multiple client requests trying to disable or enable
103       // the table at the same time. Ensure only the first request is honored
104       // After that, no other requests can be accepted until the table reaches
105       // DISABLED or ENABLED.
106       if (!skipTableStateCheck) {
107         try {
108           if (!this.assignmentManager.getTableStateManager().setTableStateIfInStates(
109               this.tableName, ZooKeeperProtos.Table.State.ENABLING,
110               ZooKeeperProtos.Table.State.DISABLED)) {
111             LOG.info("Table " + tableName + " isn't disabled; skipping enable");
112             throw new TableNotDisabledException(this.tableName);
113           }
114         } catch (CoordinatedStateException e) {
115           throw new IOException("Unable to ensure that the table will be" +
116             " enabling because of a coordination engine issue", e);
117         }
118       }
119       success = true;
120     } finally {
121       if (!success) {
122         releaseTableLock();
123       }
124     }
125     return this;
126   }
127 
128   @Override
129   public String toString() {
130     String name = "UnknownServerName";
131     if(server != null && server.getServerName() != null) {
132       name = server.getServerName().toString();
133     }
134     return getClass().getSimpleName() + "-" + name + "-" + getSeqid() + "-" +
135         tableName;
136   }
137 
138   @Override
139   public void process() {
140     try {
141       LOG.info("Attempting to enable the table " + this.tableName);
142       MasterCoprocessorHost cpHost = ((HMaster) this.server)
143           .getMasterCoprocessorHost();
144       if (cpHost != null) {
145         cpHost.preEnableTableHandler(this.tableName);
146       }
147       handleEnableTable();
148       if (cpHost != null) {
149         cpHost.postEnableTableHandler(this.tableName);
150       }
151     } catch (IOException e) {
152       LOG.error("Error trying to enable the table " + this.tableName, e);
153     } catch (CoordinatedStateException e) {
154       LOG.error("Error trying to enable the table " + this.tableName, e);
155     } catch (InterruptedException e) {
156       LOG.error("Error trying to enable the table " + this.tableName, e);
157     } finally {
158       releaseTableLock();
159     }
160   }
161 
162   private void releaseTableLock() {
163     if (this.tableLock != null) {
164       try {
165         this.tableLock.release();
166       } catch (IOException ex) {
167         LOG.warn("Could not release the table lock", ex);
168       }
169     }
170   }
171 
172   private void handleEnableTable() throws IOException, CoordinatedStateException,
173       InterruptedException {
174     // I could check table is disabling and if so, not enable but require
175     // that user first finish disabling but that might be obnoxious.
176 
177     // Set table enabling flag up in zk.
178     this.assignmentManager.getTableStateManager().setTableState(this.tableName,
179       ZooKeeperProtos.Table.State.ENABLING);
180     boolean done = false;
181     ServerManager serverManager = ((HMaster)this.server).getServerManager();
182     // Get the regions of this table. We're done when all listed
183     // tables are onlined.
184     List<Pair<HRegionInfo, ServerName>> tableRegionsAndLocations = MetaReader
185         .getTableRegionsAndLocations(this.catalogTracker, tableName, true);
186     int countOfRegionsInTable = tableRegionsAndLocations.size();
187     Map<HRegionInfo, ServerName> regionsToAssign =
188         regionsToAssignWithServerName(tableRegionsAndLocations);
189     int regionsCount = regionsToAssign.size();
190     if (regionsCount == 0) {
191       done = true;
192     }
193     LOG.info("Table '" + this.tableName + "' has " + countOfRegionsInTable
194       + " regions, of which " + regionsCount + " are offline.");
195     List<ServerName> onlineServers = serverManager.createDestinationServersList();
196     Map<ServerName, List<HRegionInfo>> bulkPlan =
197         this.assignmentManager.getBalancer().retainAssignment(regionsToAssign, onlineServers);
198     LOG.info("Bulk assigning " + regionsCount + " region(s) across " + bulkPlan.size()
199       + " server(s), retainAssignment=true");
200     
201     BulkAssigner ba = new GeneralBulkAssigner(this.server, bulkPlan, this.assignmentManager, true);
202     try {
203       if (ba.bulkAssign()) {
204         done = true;
205       }
206     } catch (InterruptedException e) {
207       LOG.warn("Enable operation was interrupted when enabling table '"
208         + this.tableName + "'");
209       // Preserve the interrupt.
210       Thread.currentThread().interrupt();
211     }
212     if (done) {
213       // Flip the table to enabled.
214       this.assignmentManager.getTableStateManager().setTableState(
215         this.tableName, ZooKeeperProtos.Table.State.ENABLED);
216       LOG.info("Table '" + this.tableName
217       + "' was successfully enabled. Status: done=" + done);
218     } else {
219       LOG.warn("Table '" + this.tableName
220       + "' wasn't successfully enabled. Status: done=" + done);
221     }
222   }
223 
224   /**
225    * @param regionsInMeta
226    * @return List of regions neither in transition nor assigned.
227    * @throws IOException
228    */
229   private Map<HRegionInfo, ServerName> regionsToAssignWithServerName(
230       final List<Pair<HRegionInfo, ServerName>> regionsInMeta) throws IOException {
231     Map<HRegionInfo, ServerName> regionsToAssign =
232         new HashMap<HRegionInfo, ServerName>(regionsInMeta.size());
233     RegionStates regionStates = this.assignmentManager.getRegionStates();
234     for (Pair<HRegionInfo, ServerName> regionLocation : regionsInMeta) {
235       HRegionInfo hri = regionLocation.getFirst();
236       ServerName sn = regionLocation.getSecond();
237       if (regionStates.isRegionOffline(hri)) {
238         regionsToAssign.put(hri, sn);
239       } else {
240         if (LOG.isDebugEnabled()) {
241           LOG.debug("Skipping assign for the region " + hri + " during enable table "
242               + hri.getTable() + " because its already in tranition or assigned.");
243         }
244       }
245     }
246     return regionsToAssign;
247   }
248 }