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