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