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  
19  package org.apache.hadoop.hbase.master.procedure;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.util.List;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.HColumnDescriptor;
30  import org.apache.hadoop.hbase.HRegionInfo;
31  import org.apache.hadoop.hbase.HTableDescriptor;
32  import org.apache.hadoop.hbase.InvalidFamilyOperationException;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.classification.InterfaceAudience;
35  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
36  import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
37  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
38  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
39  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.ModifyColumnFamilyState;
40  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
41  import org.apache.hadoop.hbase.security.User;
42  
43  /**
44   * The procedure to modify a column family from an existing table.
45   */
46  @InterfaceAudience.Private
47  public class ModifyColumnFamilyProcedure
48      extends StateMachineProcedure<MasterProcedureEnv, ModifyColumnFamilyState>
49      implements TableProcedureInterface {
50    private static final Log LOG = LogFactory.getLog(ModifyColumnFamilyProcedure.class);
51  
52    private final AtomicBoolean aborted = new AtomicBoolean(false);
53  
54    private TableName tableName;
55    private HTableDescriptor unmodifiedHTableDescriptor;
56    private HColumnDescriptor cfDescriptor;
57    private User user;
58  
59    private Boolean traceEnabled;
60  
61    public ModifyColumnFamilyProcedure() {
62      this.unmodifiedHTableDescriptor = null;
63      this.traceEnabled = null;
64    }
65  
66    public ModifyColumnFamilyProcedure(final MasterProcedureEnv env, final TableName tableName,
67        final HColumnDescriptor cfDescriptor) {
68      this.tableName = tableName;
69      this.cfDescriptor = cfDescriptor;
70      this.user = env.getRequestUser();
71      this.setOwner(this.user.getShortName());
72      this.unmodifiedHTableDescriptor = null;
73      this.traceEnabled = null;
74    }
75  
76    @Override
77    protected Flow executeFromState(final MasterProcedureEnv env,
78        final ModifyColumnFamilyState state) throws InterruptedException {
79      if (isTraceEnabled()) {
80        LOG.trace(this + " execute state=" + state);
81      }
82  
83      try {
84        switch (state) {
85        case MODIFY_COLUMN_FAMILY_PREPARE:
86          prepareModify(env);
87          setNextState(ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_PRE_OPERATION);
88          break;
89        case MODIFY_COLUMN_FAMILY_PRE_OPERATION:
90          preModify(env, state);
91          setNextState(ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR);
92          break;
93        case MODIFY_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
94          updateTableDescriptor(env);
95          setNextState(ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_POST_OPERATION);
96          break;
97        case MODIFY_COLUMN_FAMILY_POST_OPERATION:
98          postModify(env, state);
99          setNextState(ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_REOPEN_ALL_REGIONS);
100         break;
101       case MODIFY_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
102         reOpenAllRegionsIfTableIsOnline(env);
103         return Flow.NO_MORE_STATE;
104       default:
105         throw new UnsupportedOperationException(this + " unhandled state=" + state);
106       }
107     } catch (IOException e) {
108       LOG.warn("Error trying to modify the column family " + getColumnFamilyName()
109           + " of the table " + tableName + "(in state=" + state + ")", e);
110 
111       setFailure("master-modify-columnfamily", e);
112     }
113     return Flow.HAS_MORE_STATE;
114   }
115 
116   @Override
117   protected void rollbackState(final MasterProcedureEnv env, final ModifyColumnFamilyState state)
118       throws IOException {
119     if (isTraceEnabled()) {
120       LOG.trace(this + " rollback state=" + state);
121     }
122     try {
123       switch (state) {
124       case MODIFY_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
125         break; // Nothing to undo.
126       case MODIFY_COLUMN_FAMILY_POST_OPERATION:
127         // TODO-MAYBE: call the coprocessor event to undo?
128         break;
129       case MODIFY_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
130         restoreTableDescriptor(env);
131         break;
132       case MODIFY_COLUMN_FAMILY_PRE_OPERATION:
133         // TODO-MAYBE: call the coprocessor event to undo?
134         break;
135       case MODIFY_COLUMN_FAMILY_PREPARE:
136         break; // nothing to do
137       default:
138         throw new UnsupportedOperationException(this + " unhandled state=" + state);
139       }
140     } catch (IOException e) {
141       // This will be retried. Unless there is a bug in the code,
142       // this should be just a "temporary error" (e.g. network down)
143       LOG.warn("Failed rollback attempt step " + state + " for adding the column family"
144           + getColumnFamilyName() + " to the table " + tableName, e);
145       throw e;
146     }
147   }
148 
149   @Override
150   protected ModifyColumnFamilyState getState(final int stateId) {
151     return ModifyColumnFamilyState.valueOf(stateId);
152   }
153 
154   @Override
155   protected int getStateId(final ModifyColumnFamilyState state) {
156     return state.getNumber();
157   }
158 
159   @Override
160   protected ModifyColumnFamilyState getInitialState() {
161     return ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_PREPARE;
162   }
163 
164   @Override
165   protected void setNextState(ModifyColumnFamilyState state) {
166     if (aborted.get()) {
167       setAbortFailure("modify-columnfamily", "abort requested");
168     } else {
169       super.setNextState(state);
170     }
171   }
172 
173   @Override
174   public boolean abort(final MasterProcedureEnv env) {
175     aborted.set(true);
176     return true;
177   }
178 
179   @Override
180   protected boolean acquireLock(final MasterProcedureEnv env) {
181     if (env.waitInitialized(this)) return false;
182     return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, tableName);
183   }
184 
185   @Override
186   protected void releaseLock(final MasterProcedureEnv env) {
187     env.getProcedureQueue().releaseTableExclusiveLock(this, tableName);
188   }
189 
190   @Override
191   public void serializeStateData(final OutputStream stream) throws IOException {
192     super.serializeStateData(stream);
193 
194     MasterProcedureProtos.ModifyColumnFamilyStateData.Builder modifyCFMsg =
195         MasterProcedureProtos.ModifyColumnFamilyStateData.newBuilder()
196             .setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
197             .setTableName(ProtobufUtil.toProtoTableName(tableName))
198             .setColumnfamilySchema(cfDescriptor.convert());
199     if (unmodifiedHTableDescriptor != null) {
200       modifyCFMsg.setUnmodifiedTableSchema(unmodifiedHTableDescriptor.convert());
201     }
202 
203     modifyCFMsg.build().writeDelimitedTo(stream);
204   }
205 
206   @Override
207   public void deserializeStateData(final InputStream stream) throws IOException {
208     super.deserializeStateData(stream);
209 
210     MasterProcedureProtos.ModifyColumnFamilyStateData modifyCFMsg =
211         MasterProcedureProtos.ModifyColumnFamilyStateData.parseDelimitedFrom(stream);
212     user = MasterProcedureUtil.toUserInfo(modifyCFMsg.getUserInfo());
213     tableName = ProtobufUtil.toTableName(modifyCFMsg.getTableName());
214     cfDescriptor = HColumnDescriptor.convert(modifyCFMsg.getColumnfamilySchema());
215     if (modifyCFMsg.hasUnmodifiedTableSchema()) {
216       unmodifiedHTableDescriptor = HTableDescriptor.convert(modifyCFMsg.getUnmodifiedTableSchema());
217     }
218   }
219 
220   @Override
221   public void toStringClassDetails(StringBuilder sb) {
222     sb.append(getClass().getSimpleName());
223     sb.append(" (table=");
224     sb.append(tableName);
225     sb.append(", columnfamily=");
226     if (cfDescriptor != null) {
227       sb.append(getColumnFamilyName());
228     } else {
229       sb.append("Unknown");
230     }
231     sb.append(")");
232   }
233 
234   @Override
235   public TableName getTableName() {
236     return tableName;
237   }
238 
239   @Override
240   public TableOperationType getTableOperationType() {
241     return TableOperationType.EDIT;
242   }
243 
244   /**
245    * Action before any real action of modifying column family.
246    * @param env MasterProcedureEnv
247    * @throws IOException
248    */
249   private void prepareModify(final MasterProcedureEnv env) throws IOException {
250     // Checks whether the table is allowed to be modified.
251     MasterDDLOperationHelper.checkTableModifiable(env, tableName);
252 
253     unmodifiedHTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
254     if (unmodifiedHTableDescriptor == null) {
255       throw new IOException("HTableDescriptor missing for " + tableName);
256     }
257     if (!unmodifiedHTableDescriptor.hasFamily(cfDescriptor.getName())) {
258       throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
259           + "' does not exist, so it cannot be modified");
260     }
261   }
262 
263   /**
264    * Action before modifying column family.
265    * @param env MasterProcedureEnv
266    * @param state the procedure state
267    * @throws IOException
268    * @throws InterruptedException
269    */
270   private void preModify(final MasterProcedureEnv env, final ModifyColumnFamilyState state)
271       throws IOException, InterruptedException {
272     runCoprocessorAction(env, state);
273   }
274 
275   /**
276    * Modify the column family from the file system
277    */
278   private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
279     // Update table descriptor
280     LOG.info("ModifyColumnFamily. Table = " + tableName + " HCD = " + cfDescriptor.toString());
281 
282     HTableDescriptor htd = env.getMasterServices().getTableDescriptors().get(tableName);
283     htd.modifyFamily(cfDescriptor);
284     env.getMasterServices().getTableDescriptors().add(htd);
285   }
286 
287   /**
288    * Restore back to the old descriptor
289    * @param env MasterProcedureEnv
290    * @throws IOException
291    **/
292   private void restoreTableDescriptor(final MasterProcedureEnv env) throws IOException {
293     env.getMasterServices().getTableDescriptors().add(unmodifiedHTableDescriptor);
294 
295     // Make sure regions are opened after table descriptor is updated.
296     reOpenAllRegionsIfTableIsOnline(env);
297   }
298 
299   /**
300    * Action after modifying column family.
301    * @param env MasterProcedureEnv
302    * @param state the procedure state
303    * @throws IOException
304    * @throws InterruptedException
305    */
306   private void postModify(final MasterProcedureEnv env, final ModifyColumnFamilyState state)
307       throws IOException, InterruptedException {
308     runCoprocessorAction(env, state);
309   }
310 
311   /**
312    * Last action from the procedure - executed when online schema change is supported.
313    * @param env MasterProcedureEnv
314    * @throws IOException
315    */
316   private void reOpenAllRegionsIfTableIsOnline(final MasterProcedureEnv env) throws IOException {
317     // This operation only run when the table is enabled.
318     if (!env.getMasterServices().getAssignmentManager().getTableStateManager()
319         .isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
320       return;
321     }
322 
323     List<HRegionInfo> regionInfoList = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
324     if (MasterDDLOperationHelper.reOpenAllRegions(env, getTableName(), regionInfoList)) {
325       LOG.info("Completed add column family operation on table " + getTableName());
326     } else {
327       LOG.warn("Error on reopening the regions on table " + getTableName());
328     }
329   }
330 
331   /**
332    * The procedure could be restarted from a different machine. If the variable is null, we need to
333    * retrieve it.
334    * @return traceEnabled
335    */
336   private Boolean isTraceEnabled() {
337     if (traceEnabled == null) {
338       traceEnabled = LOG.isTraceEnabled();
339     }
340     return traceEnabled;
341   }
342 
343   private String getColumnFamilyName() {
344     return cfDescriptor.getNameAsString();
345   }
346 
347   /**
348    * Coprocessor Action.
349    * @param env MasterProcedureEnv
350    * @param state the procedure state
351    * @throws IOException
352    * @throws InterruptedException
353    */
354   private void runCoprocessorAction(final MasterProcedureEnv env,
355       final ModifyColumnFamilyState state) throws IOException, InterruptedException {
356     final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
357     if (cpHost != null) {
358       switch (state) {
359         case MODIFY_COLUMN_FAMILY_PRE_OPERATION:
360           cpHost.preModifyColumnHandler(tableName, cfDescriptor, user);
361           break;
362         case MODIFY_COLUMN_FAMILY_POST_OPERATION:
363           cpHost.postModifyColumnHandler(tableName, cfDescriptor, user);
364           break;
365         default:
366           throw new UnsupportedOperationException(this + " unhandled state=" + state);
367       }
368     }
369   }
370 }