1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.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.DeleteColumnFamilyState;
40 import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
41 import org.apache.hadoop.hbase.util.ByteStringer;
42 import org.apache.hadoop.hbase.util.Bytes;
43 import org.apache.hadoop.security.UserGroupInformation;
44
45
46
47
48 @InterfaceAudience.Private
49 public class DeleteColumnFamilyProcedure
50 extends StateMachineProcedure<MasterProcedureEnv, DeleteColumnFamilyState>
51 implements TableProcedureInterface {
52 private static final Log LOG = LogFactory.getLog(DeleteColumnFamilyProcedure.class);
53
54 private final AtomicBoolean aborted = new AtomicBoolean(false);
55
56 private HTableDescriptor unmodifiedHTableDescriptor;
57 private TableName tableName;
58 private byte [] familyName;
59 private UserGroupInformation user;
60
61 private List<HRegionInfo> regionInfoList;
62 private Boolean traceEnabled;
63
64 public DeleteColumnFamilyProcedure() {
65 this.unmodifiedHTableDescriptor = null;
66 this.regionInfoList = null;
67 this.traceEnabled = null;
68 }
69
70 public DeleteColumnFamilyProcedure(final MasterProcedureEnv env, final TableName tableName,
71 final byte[] familyName) {
72 this.tableName = tableName;
73 this.familyName = familyName;
74 this.user = env.getRequestUser().getUGI();
75 this.setOwner(this.user.getShortUserName());
76 this.unmodifiedHTableDescriptor = null;
77 this.regionInfoList = null;
78 this.traceEnabled = null;
79 }
80
81 @Override
82 protected Flow executeFromState(final MasterProcedureEnv env, DeleteColumnFamilyState state)
83 throws InterruptedException {
84 if (isTraceEnabled()) {
85 LOG.trace(this + " execute state=" + state);
86 }
87
88 try {
89 switch (state) {
90 case DELETE_COLUMN_FAMILY_PREPARE:
91 prepareDelete(env);
92 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_PRE_OPERATION);
93 break;
94 case DELETE_COLUMN_FAMILY_PRE_OPERATION:
95 preDelete(env, state);
96 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR);
97 break;
98 case DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
99 updateTableDescriptor(env);
100 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT);
101 break;
102 case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
103 deleteFromFs(env);
104 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_POST_OPERATION);
105 break;
106 case DELETE_COLUMN_FAMILY_POST_OPERATION:
107 postDelete(env, state);
108 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS);
109 break;
110 case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
111 reOpenAllRegionsIfTableIsOnline(env);
112 return Flow.NO_MORE_STATE;
113 default:
114 throw new UnsupportedOperationException(this + " unhandled state=" + state);
115 }
116 } catch (IOException e) {
117 if (!isRollbackSupported(state)) {
118
119 LOG.warn("Error trying to delete the column family " + getColumnFamilyName()
120 + " from table " + tableName + "(in state=" + state + ")", e);
121 } else {
122 LOG.error("Error trying to delete the column family " + getColumnFamilyName()
123 + " from table " + tableName + "(in state=" + state + ")", e);
124 setFailure("master-delete-column-family", e);
125 }
126 }
127 return Flow.HAS_MORE_STATE;
128 }
129
130 @Override
131 protected void rollbackState(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
132 throws IOException {
133 if (isTraceEnabled()) {
134 LOG.trace(this + " rollback state=" + state);
135 }
136 try {
137 switch (state) {
138 case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
139 break;
140 case DELETE_COLUMN_FAMILY_POST_OPERATION:
141
142 break;
143 case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
144
145
146
147 throw new UnsupportedOperationException(this + " rollback of state=" + state
148 + " is unsupported.");
149 case DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
150 restoreTableDescriptor(env);
151 break;
152 case DELETE_COLUMN_FAMILY_PRE_OPERATION:
153
154 break;
155 case DELETE_COLUMN_FAMILY_PREPARE:
156 break;
157 default:
158 throw new UnsupportedOperationException(this + " unhandled state=" + state);
159 }
160 } catch (IOException e) {
161
162
163 LOG.warn("Failed rollback attempt step " + state + " for deleting the column family"
164 + getColumnFamilyName() + " to the table " + tableName, e);
165 throw e;
166 }
167 }
168
169 @Override
170 protected DeleteColumnFamilyState getState(final int stateId) {
171 return DeleteColumnFamilyState.valueOf(stateId);
172 }
173
174 @Override
175 protected int getStateId(final DeleteColumnFamilyState state) {
176 return state.getNumber();
177 }
178
179 @Override
180 protected DeleteColumnFamilyState getInitialState() {
181 return DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_PREPARE;
182 }
183
184 @Override
185 protected void setNextState(DeleteColumnFamilyState state) {
186 if (aborted.get() && isRollbackSupported(state)) {
187 setAbortFailure("delete-columnfamily", "abort requested");
188 } else {
189 super.setNextState(state);
190 }
191 }
192
193 @Override
194 public boolean abort(final MasterProcedureEnv env) {
195 aborted.set(true);
196 return true;
197 }
198
199 @Override
200 protected boolean acquireLock(final MasterProcedureEnv env) {
201 if (env.waitInitialized(this)) return false;
202 return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, tableName);
203 }
204
205 @Override
206 protected void releaseLock(final MasterProcedureEnv env) {
207 env.getProcedureQueue().releaseTableExclusiveLock(this, tableName);
208 }
209
210 @Override
211 public void serializeStateData(final OutputStream stream) throws IOException {
212 super.serializeStateData(stream);
213
214 MasterProcedureProtos.DeleteColumnFamilyStateData.Builder deleteCFMsg =
215 MasterProcedureProtos.DeleteColumnFamilyStateData.newBuilder()
216 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
217 .setTableName(ProtobufUtil.toProtoTableName(tableName))
218 .setColumnfamilyName(ByteStringer.wrap(familyName));
219 if (unmodifiedHTableDescriptor != null) {
220 deleteCFMsg.setUnmodifiedTableSchema(unmodifiedHTableDescriptor.convert());
221 }
222
223 deleteCFMsg.build().writeDelimitedTo(stream);
224 }
225
226 @Override
227 public void deserializeStateData(final InputStream stream) throws IOException {
228 super.deserializeStateData(stream);
229 MasterProcedureProtos.DeleteColumnFamilyStateData deleteCFMsg =
230 MasterProcedureProtos.DeleteColumnFamilyStateData.parseDelimitedFrom(stream);
231 user = MasterProcedureUtil.toUserInfo(deleteCFMsg.getUserInfo());
232 tableName = ProtobufUtil.toTableName(deleteCFMsg.getTableName());
233 familyName = deleteCFMsg.getColumnfamilyName().toByteArray();
234
235 if (deleteCFMsg.hasUnmodifiedTableSchema()) {
236 unmodifiedHTableDescriptor = HTableDescriptor.convert(deleteCFMsg.getUnmodifiedTableSchema());
237 }
238 }
239
240 @Override
241 public void toStringClassDetails(StringBuilder sb) {
242 sb.append(getClass().getSimpleName());
243 sb.append(" (table=");
244 sb.append(tableName);
245 sb.append(", columnfamily=");
246 if (familyName != null) {
247 sb.append(getColumnFamilyName());
248 } else {
249 sb.append("Unknown");
250 }
251 sb.append(")");
252 }
253
254 @Override
255 public TableName getTableName() {
256 return tableName;
257 }
258
259 @Override
260 public TableOperationType getTableOperationType() {
261 return TableOperationType.EDIT;
262 }
263
264
265
266
267
268
269 private void prepareDelete(final MasterProcedureEnv env) throws IOException {
270
271 MasterDDLOperationHelper.checkTableModifiable(env, tableName);
272
273
274 unmodifiedHTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
275 if (unmodifiedHTableDescriptor == null) {
276 throw new IOException("HTableDescriptor missing for " + tableName);
277 }
278 if (!unmodifiedHTableDescriptor.hasFamily(familyName)) {
279 throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
280 + "' does not exist, so it cannot be deleted");
281 }
282
283 if (unmodifiedHTableDescriptor.getColumnFamilies().length == 1) {
284 throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
285 + "' is the only column family in the table, so it cannot be deleted");
286 }
287 }
288
289
290
291
292
293
294
295
296 private void preDelete(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
297 throws IOException, InterruptedException {
298 runCoprocessorAction(env, state);
299 }
300
301
302
303
304 private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
305
306 LOG.info("DeleteColumn. Table = " + tableName + " family = " + getColumnFamilyName());
307
308 HTableDescriptor htd = env.getMasterServices().getTableDescriptors().get(tableName);
309
310 if (!htd.hasFamily(familyName)) {
311
312
313
314 return;
315 }
316
317 htd.removeFamily(familyName);
318 env.getMasterServices().getTableDescriptors().add(htd);
319 }
320
321
322
323
324
325
326 private void restoreTableDescriptor(final MasterProcedureEnv env) throws IOException {
327 env.getMasterServices().getTableDescriptors().add(unmodifiedHTableDescriptor);
328
329
330 reOpenAllRegionsIfTableIsOnline(env);
331 }
332
333
334
335
336 private void deleteFromFs(final MasterProcedureEnv env) throws IOException {
337 MasterDDLOperationHelper.deleteColumnFamilyFromFileSystem(env, tableName,
338 getRegionInfoList(env), familyName);
339 }
340
341
342
343
344
345
346
347
348 private void postDelete(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
349 throws IOException, InterruptedException {
350 runCoprocessorAction(env, state);
351 }
352
353
354
355
356
357
358 private void reOpenAllRegionsIfTableIsOnline(final MasterProcedureEnv env) throws IOException {
359
360 if (!env.getMasterServices().getAssignmentManager().getTableStateManager()
361 .isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
362 return;
363 }
364
365 if (MasterDDLOperationHelper.reOpenAllRegions(env, getTableName(), getRegionInfoList(env))) {
366 LOG.info("Completed delete column family operation on table " + getTableName());
367 } else {
368 LOG.warn("Error on reopening the regions on table " + getTableName());
369 }
370 }
371
372
373
374
375
376
377 private Boolean isTraceEnabled() {
378 if (traceEnabled == null) {
379 traceEnabled = LOG.isTraceEnabled();
380 }
381 return traceEnabled;
382 }
383
384 private String getColumnFamilyName() {
385 return Bytes.toString(familyName);
386 }
387
388
389
390
391
392
393
394
395 private void runCoprocessorAction(final MasterProcedureEnv env,
396 final DeleteColumnFamilyState state) throws IOException, InterruptedException {
397 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
398 if (cpHost != null) {
399 user.doAs(new PrivilegedExceptionAction<Void>() {
400 @Override
401 public Void run() throws Exception {
402 switch (state) {
403 case DELETE_COLUMN_FAMILY_PRE_OPERATION:
404 cpHost.preDeleteColumnHandler(tableName, familyName);
405 break;
406 case DELETE_COLUMN_FAMILY_POST_OPERATION:
407 cpHost.postDeleteColumnHandler(tableName, familyName);
408 break;
409 default:
410 throw new UnsupportedOperationException(this + " unhandled state=" + state);
411 }
412 return null;
413 }
414 });
415 }
416 }
417
418
419
420
421 private boolean isRollbackSupported(final DeleteColumnFamilyState state) {
422 switch (state) {
423 case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
424 case DELETE_COLUMN_FAMILY_POST_OPERATION:
425 case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
426
427 return false;
428 default:
429 break;
430 }
431 return true;
432 }
433
434 private List<HRegionInfo> getRegionInfoList(final MasterProcedureEnv env) throws IOException {
435 if (regionInfoList == null) {
436 regionInfoList = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
437 }
438 return regionInfoList;
439 }
440 }