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