001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver.storefiletracker;
019
020import java.io.IOException;
021import org.apache.hadoop.conf.Configuration;
022import org.apache.hadoop.hbase.DoNotRetryIOException;
023import org.apache.hadoop.hbase.TableNotEnabledException;
024import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
025import org.apache.hadoop.hbase.client.TableDescriptor;
026import org.apache.hadoop.hbase.regionserver.StoreUtils;
027import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory.Trackers;
028import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
029import org.apache.yetus.audience.InterfaceAudience;
030
031@InterfaceAudience.Private
032public final class StoreFileTrackerValidationUtils {
033
034  private StoreFileTrackerValidationUtils() {
035  }
036
037  // should not use MigrationStoreFileTracker for new family
038  private static void checkForNewFamily(Configuration conf, TableDescriptor table,
039    ColumnFamilyDescriptor family) throws IOException {
040    Configuration mergedConf = StoreUtils.createStoreConfiguration(conf, table, family);
041    Class<? extends StoreFileTracker> tracker = StoreFileTrackerFactory.getTrackerClass(mergedConf);
042    if (MigrationStoreFileTracker.class.isAssignableFrom(tracker)) {
043      throw new DoNotRetryIOException(
044        "Should not use " + Trackers.MIGRATION + " as store file tracker for new family "
045          + family.getNameAsString() + " of table " + table.getTableName());
046    }
047  }
048
049  /**
050   * Pre check when creating a new table.
051   * <p/>
052   * For now, only make sure that we do not use {@link Trackers#MIGRATION} for newly created tables.
053   * @throws IOException when there are check errors, the upper layer should fail the
054   *                     {@code CreateTableProcedure}.
055   */
056  public static void checkForCreateTable(Configuration conf, TableDescriptor table)
057    throws IOException {
058    for (ColumnFamilyDescriptor family : table.getColumnFamilies()) {
059      checkForNewFamily(conf, table, family);
060    }
061  }
062
063  /**
064   * Pre check when modifying a table.
065   * <p/>
066   * The basic idea is when you want to change the store file tracker implementation, you should use
067   * {@link Trackers#MIGRATION} first and then change to the destination store file tracker
068   * implementation.
069   * <p/>
070   * There are several rules:
071   * <ul>
072   * <li>For newly added family, you should not use {@link Trackers#MIGRATION}.</li>
073   * <li>For modifying a family:
074   * <ul>
075   * <li>If old tracker is {@link Trackers#MIGRATION}, then:
076   * <ul>
077   * <li>The new tracker is also {@link Trackers#MIGRATION}, then they must have the same src and
078   * dst tracker.</li>
079   * <li>The new tracker is not {@link Trackers#MIGRATION}, then the new tracker must be the dst
080   * tracker of the old tracker.</li>
081   * </ul>
082   * </li>
083   * <li>If the old tracker is not {@link Trackers#MIGRATION}, then:
084   * <ul>
085   * <li>If the new tracker is {@link Trackers#MIGRATION}, then the old tracker must be the src
086   * tracker of the new tracker.</li>
087   * <li>If the new tracker is not {@link Trackers#MIGRATION}, then the new tracker must be the same
088   * with old tracker.</li>
089   * </ul>
090   * </li>
091   * </ul>
092   * </li>
093   * </ul>
094   * @throws IOException when there are check errors, the upper layer should fail the
095   *                     {@code ModifyTableProcedure}.
096   */
097  public static void checkForModifyTable(Configuration conf, TableDescriptor oldTable,
098    TableDescriptor newTable, boolean isTableDisabled) throws IOException {
099    for (ColumnFamilyDescriptor newFamily : newTable.getColumnFamilies()) {
100      ColumnFamilyDescriptor oldFamily = oldTable.getColumnFamily(newFamily.getName());
101      if (oldFamily == null) {
102        checkForNewFamily(conf, newTable, newFamily);
103        continue;
104      }
105      Configuration oldConf = StoreUtils.createStoreConfiguration(conf, oldTable, oldFamily);
106      Configuration newConf = StoreUtils.createStoreConfiguration(conf, newTable, newFamily);
107
108      Class<? extends StoreFileTracker> oldTracker =
109        StoreFileTrackerFactory.getTrackerClass(oldConf);
110      Class<? extends StoreFileTracker> newTracker =
111        StoreFileTrackerFactory.getTrackerClass(newConf);
112
113      if (MigrationStoreFileTracker.class.isAssignableFrom(oldTracker)) {
114        Class<? extends StoreFileTracker> oldSrcTracker =
115          MigrationStoreFileTracker.getSrcTrackerClass(oldConf);
116        Class<? extends StoreFileTracker> oldDstTracker =
117          MigrationStoreFileTracker.getDstTrackerClass(oldConf);
118        if (oldTracker.equals(newTracker)) {
119          // confirm that we have the same src tracker and dst tracker
120          Class<? extends StoreFileTracker> newSrcTracker =
121            MigrationStoreFileTracker.getSrcTrackerClass(newConf);
122          if (!oldSrcTracker.equals(newSrcTracker)) {
123            throw new DoNotRetryIOException("The src tracker has been changed from "
124              + StoreFileTrackerFactory.getStoreFileTrackerName(oldSrcTracker) + " to "
125              + StoreFileTrackerFactory.getStoreFileTrackerName(newSrcTracker) + " for family "
126              + newFamily.getNameAsString() + " of table " + newTable.getTableName());
127          }
128          Class<? extends StoreFileTracker> newDstTracker =
129            MigrationStoreFileTracker.getDstTrackerClass(newConf);
130          if (!oldDstTracker.equals(newDstTracker)) {
131            throw new DoNotRetryIOException("The dst tracker has been changed from "
132              + StoreFileTrackerFactory.getStoreFileTrackerName(oldDstTracker) + " to "
133              + StoreFileTrackerFactory.getStoreFileTrackerName(newDstTracker) + " for family "
134              + newFamily.getNameAsString() + " of table " + newTable.getTableName());
135          }
136        } else {
137          // do not allow changing from MIGRATION to its dst SFT implementation while the table is
138          // disabled. We need to open the HRegion to migrate the tracking information while the SFT
139          // implementation is MIGRATION, otherwise we may loss data. See HBASE-26611 for more
140          // details.
141          if (isTableDisabled) {
142            throw new TableNotEnabledException(
143              "Should not change store file tracker implementation from "
144                + StoreFileTrackerFactory.Trackers.MIGRATION.name() + " while table "
145                + newTable.getTableName() + " is disabled");
146          }
147          // we can only change to the dst tracker
148          if (!newTracker.equals(oldDstTracker)) {
149            throw new DoNotRetryIOException("Should migrate tracker to "
150              + StoreFileTrackerFactory.getStoreFileTrackerName(oldDstTracker) + " but got "
151              + StoreFileTrackerFactory.getStoreFileTrackerName(newTracker) + " for family "
152              + newFamily.getNameAsString() + " of table " + newTable.getTableName());
153          }
154        }
155      } else {
156        if (!oldTracker.equals(newTracker)) {
157          // can only change to MigrationStoreFileTracker and the src tracker should be the old
158          // tracker
159          if (!MigrationStoreFileTracker.class.isAssignableFrom(newTracker)) {
160            throw new DoNotRetryIOException(
161              "Should change to " + Trackers.MIGRATION + " first when migrating from "
162                + StoreFileTrackerFactory.getStoreFileTrackerName(oldTracker) + " for family "
163                + newFamily.getNameAsString() + " of table " + newTable.getTableName());
164          }
165          // here we do not check whether the table is disabled, as after changing to MIGRATION, we
166          // still rely on the src SFT implementation to actually load the store files, so there
167          // will be no data loss problem.
168          Class<? extends StoreFileTracker> newSrcTracker =
169            MigrationStoreFileTracker.getSrcTrackerClass(newConf);
170          if (!oldTracker.equals(newSrcTracker)) {
171            throw new DoNotRetryIOException("Should use src tracker "
172              + StoreFileTrackerFactory.getStoreFileTrackerName(oldTracker) + " first but got "
173              + StoreFileTrackerFactory.getStoreFileTrackerName(newSrcTracker)
174              + " when migrating from "
175              + StoreFileTrackerFactory.getStoreFileTrackerName(oldTracker) + " for family "
176              + newFamily.getNameAsString() + " of table " + newTable.getTableName());
177          }
178          Class<? extends StoreFileTracker> newDstTracker =
179            MigrationStoreFileTracker.getDstTrackerClass(newConf);
180          // the src and dst tracker should not be the same
181          if (newSrcTracker.equals(newDstTracker)) {
182            throw new DoNotRetryIOException("The src tracker and dst tracker are both "
183              + StoreFileTrackerFactory.getStoreFileTrackerName(newSrcTracker) + " for family "
184              + newFamily.getNameAsString() + " of table " + newTable.getTableName());
185          }
186        }
187      }
188    }
189  }
190
191  /**
192   * Makes sure restoring a snapshot does not break the current SFT setup follows
193   * StoreUtils.createStoreConfiguration
194   * @param currentTableDesc  Existing Table's TableDescriptor
195   * @param snapshotTableDesc Snapshot's TableDescriptor
196   * @param baseConf          Current global configuration
197   * @throws RestoreSnapshotException if restore would break the current SFT setup
198   */
199  public static void validatePreRestoreSnapshot(TableDescriptor currentTableDesc,
200    TableDescriptor snapshotTableDesc, Configuration baseConf) throws RestoreSnapshotException {
201
202    for (ColumnFamilyDescriptor cfDesc : currentTableDesc.getColumnFamilies()) {
203      ColumnFamilyDescriptor snapCFDesc = snapshotTableDesc.getColumnFamily(cfDesc.getName());
204      // if there is no counterpart in the snapshot it will be just deleted so the config does
205      // not matter
206      if (snapCFDesc != null) {
207        Configuration currentCompositeConf =
208          StoreUtils.createStoreConfiguration(baseConf, currentTableDesc, cfDesc);
209        Configuration snapCompositeConf =
210          StoreUtils.createStoreConfiguration(baseConf, snapshotTableDesc, snapCFDesc);
211        Class<? extends StoreFileTracker> currentSFT =
212          StoreFileTrackerFactory.getTrackerClass(currentCompositeConf);
213        Class<? extends StoreFileTracker> snapSFT =
214          StoreFileTrackerFactory.getTrackerClass(snapCompositeConf);
215
216        // restoration is not possible if there is an SFT mismatch
217        if (currentSFT != snapSFT) {
218          throw new RestoreSnapshotException(
219            "Restoring Snapshot is not possible because " + " the config for column family "
220              + cfDesc.getNameAsString() + " has incompatible configuration. Current SFT: "
221              + currentSFT + " SFT from snapshot: " + snapSFT);
222        }
223      }
224    }
225  }
226}