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.List;
23  import java.util.concurrent.ExecutorService;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.hbase.CoordinatedStateException;
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.TableNotEnabledException;
33  import org.apache.hadoop.hbase.TableNotFoundException;
34  import org.apache.hadoop.hbase.MetaTableAccessor;
35  import org.apache.hadoop.hbase.constraint.ConstraintException;
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.RegionStates;
43  import org.apache.hadoop.hbase.master.TableLockManager;
44  import org.apache.hadoop.hbase.master.RegionState.State;
45  import org.apache.hadoop.hbase.master.TableLockManager.TableLock;
46  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
47  import org.apache.htrace.Trace;
48  
49  /**
50   * Handler to run disable of a table.
51   */
52  @InterfaceAudience.Private
53  public class DisableTableHandler extends EventHandler {
54    private static final Log LOG = LogFactory.getLog(DisableTableHandler.class);
55    private final TableName tableName;
56    private final AssignmentManager assignmentManager;
57    private final TableLockManager tableLockManager;
58    private final boolean skipTableStateCheck;
59    private TableLock tableLock;
60  
61    public DisableTableHandler(Server server, TableName tableName,
62        AssignmentManager assignmentManager, TableLockManager tableLockManager,
63        boolean skipTableStateCheck) {
64      super(server, EventType.C_M_DISABLE_TABLE);
65      this.tableName = tableName;
66      this.assignmentManager = assignmentManager;
67      this.tableLockManager = tableLockManager;
68      this.skipTableStateCheck = skipTableStateCheck;
69    }
70  
71    public DisableTableHandler prepare()
72        throws TableNotFoundException, TableNotEnabledException, IOException {
73      if(tableName.equals(TableName.META_TABLE_NAME)) {
74        throw new ConstraintException("Cannot disable catalog table");
75      }
76      //acquire the table write lock, blocking
77      this.tableLock = this.tableLockManager.writeLock(tableName,
78          EventType.C_M_DISABLE_TABLE.toString());
79      this.tableLock.acquire();
80  
81      boolean success = false;
82      try {
83        // Check if table exists
84        if (!MetaTableAccessor.tableExists(this.server.getConnection(), tableName)) {
85          throw new TableNotFoundException(tableName);
86        }
87  
88        // There could be multiple client requests trying to disable or enable
89        // the table at the same time. Ensure only the first request is honored
90        // After that, no other requests can be accepted until the table reaches
91        // DISABLED or ENABLED.
92        //TODO: reevaluate this since we have table locks now
93        if (!skipTableStateCheck) {
94          try {
95            if (!this.assignmentManager.getTableStateManager().setTableStateIfInStates(
96              this.tableName, ZooKeeperProtos.Table.State.DISABLING,
97              ZooKeeperProtos.Table.State.ENABLED)) {
98              LOG.info("Table " + tableName + " isn't enabled; skipping disable");
99              throw new TableNotEnabledException(this.tableName);
100           }
101         } catch (CoordinatedStateException e) {
102           throw new IOException("Unable to ensure that the table will be" +
103             " disabling because of a coordination engine issue", e);
104         }
105       }
106       success = true;
107     } finally {
108       if (!success) {
109         releaseTableLock();
110       }
111     }
112 
113     return this;
114   }
115 
116   @Override
117   public String toString() {
118     String name = "UnknownServerName";
119     if(server != null && server.getServerName() != null) {
120       name = server.getServerName().toString();
121     }
122     return getClass().getSimpleName() + "-" + name + "-" + getSeqid() + "-" +
123         tableName;
124   }
125 
126   @Override
127   public void process() {
128     try {
129       LOG.info("Attempting to disable table " + this.tableName);
130       MasterCoprocessorHost cpHost = ((HMaster) this.server)
131           .getMasterCoprocessorHost();
132       // this executes in assignment manager to recover disabling table, not overriding user
133       if (cpHost != null) {
134         cpHost.preDisableTableHandler(this.tableName, null);
135       }
136       handleDisableTable();
137       if (cpHost != null) {
138         cpHost.postDisableTableHandler(this.tableName, null);
139       }
140     } catch (IOException e) {
141       LOG.error("Error trying to disable table " + this.tableName, e);
142     } catch (CoordinatedStateException e) {
143       LOG.error("Error trying to disable table " + this.tableName, e);
144     } finally {
145       releaseTableLock();
146     }
147   }
148 
149   private void releaseTableLock() {
150     if (this.tableLock != null) {
151       try {
152         this.tableLock.release();
153       } catch (IOException ex) {
154         LOG.warn("Could not release the table lock", ex);
155       }
156     }
157   }
158 
159   private void handleDisableTable() throws IOException, CoordinatedStateException {
160     // Set table disabling flag up in zk.
161     this.assignmentManager.getTableStateManager().setTableState(this.tableName,
162       ZooKeeperProtos.Table.State.DISABLING);
163     boolean done = false;
164     while (true) {
165       // Get list of online regions that are of this table.  Regions that are
166       // already closed will not be included in this list; i.e. the returned
167       // list is not ALL regions in a table, its all online regions according
168       // to the in-memory state on this master.
169       final List<HRegionInfo> regions = this.assignmentManager
170         .getRegionStates().getRegionsOfTable(tableName);
171       if (regions.size() == 0) {
172         done = true;
173         break;
174       }
175       LOG.info("Offlining " + regions.size() + " regions.");
176       BulkDisabler bd = new BulkDisabler(this.server, regions);
177       try {
178         if (bd.bulkAssign()) {
179           done = true;
180           break;
181         }
182       } catch (InterruptedException e) {
183         LOG.warn("Disable was interrupted");
184         // Preserve the interrupt.
185         Thread.currentThread().interrupt();
186         break;
187       }
188     }
189     // Flip the table to disabled if success.
190     if (done) this.assignmentManager.getTableStateManager().setTableState(this.tableName,
191       ZooKeeperProtos.Table.State.DISABLED);
192     LOG.info("Disabled table, " + this.tableName + ", is done=" + done);
193   }
194 
195   /**
196    * Run bulk disable.
197    */
198   class BulkDisabler extends BulkAssigner {
199     private final List<HRegionInfo> regions;
200 
201     BulkDisabler(final Server server, final List<HRegionInfo> regions) {
202       super(server);
203       this.regions = regions;
204     }
205 
206     @Override
207     protected void populatePool(ExecutorService pool) {
208       RegionStates regionStates = assignmentManager.getRegionStates();
209       for (HRegionInfo region: regions) {
210         if (regionStates.isRegionInTransition(region)
211             && !regionStates.isRegionInState(region, State.FAILED_CLOSE)) {
212           continue;
213         }
214         final HRegionInfo hri = region;
215         pool.execute(Trace.wrap("DisableTableHandler.BulkDisabler",new Runnable() {
216           public void run() {
217             assignmentManager.unassign(hri, true);
218           }
219         }));
220       }
221     }
222 
223     @Override
224     protected boolean waitUntilDone(long timeout)
225     throws InterruptedException {
226       long startTime = System.currentTimeMillis();
227       long remaining = timeout;
228       List<HRegionInfo> regions = null;
229       long lastLogTime = startTime;
230       while (!server.isStopped() && remaining > 0) {
231         Thread.sleep(waitingTimeForEvents);
232         regions = assignmentManager.getRegionStates().getRegionsOfTable(tableName);
233         long now = System.currentTimeMillis();
234         // Don't log more than once every ten seconds. Its obnoxious. And only log table regions
235         // if we are waiting a while for them to go down...
236         if (LOG.isDebugEnabled() && ((now - lastLogTime) > 10000)) {
237           lastLogTime =  now;
238           LOG.debug("Disable waiting until done; " + remaining + " ms remaining; " + regions);
239         }
240         if (regions.isEmpty()) break;
241         remaining = timeout - (now - startTime);
242       }
243       return regions != null && regions.isEmpty();
244     }
245   }
246 }