View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.wal;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.List;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.fs.FileSystem;
31  import org.apache.hadoop.fs.Path;
32  import org.apache.hadoop.hbase.HConstants;
33  import org.apache.hadoop.hbase.util.CommonFSUtils;
34  import org.apache.hadoop.hbase.wal.WAL.Entry;
35  
36  import static org.apache.hadoop.hbase.wal.DefaultWALProvider.DEFAULT_PROVIDER_ID;
37  import static org.apache.hadoop.hbase.wal.DefaultWALProvider.META_WAL_PROVIDER_ID;
38  import static org.apache.hadoop.hbase.wal.DefaultWALProvider.WAL_FILE_NAME_DELIMITER;
39  
40  // imports for things that haven't moved from regionserver.wal yet.
41  import org.apache.hadoop.hbase.regionserver.wal.FSHLog;
42  import org.apache.hadoop.hbase.regionserver.wal.ProtobufLogWriter;
43  import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener;
44  
45  /**
46   * A WAL Provider that returns a single thread safe WAL that optionally can skip parts of our
47   * normal interactions with HDFS.
48   *
49   * This implementation picks a directory in HDFS based on the same mechanisms as the 
50   * {@link DefaultWALProvider}. Users can configure how much interaction
51   * we have with HDFS with the configuration property "hbase.wal.iotestprovider.operations".
52   * The value should be a comma separated list of allowed operations:
53   * <ul>
54   *   <li><em>append</em>   : edits will be written to the underlying filesystem
55   *   <li><em>sync</em>     : wal syncs will result in hflush calls
56   *   <li><em>fileroll</em> : roll requests will result in creating a new file on the underlying
57   *                           filesystem.
58   * </ul>
59   * Additionally, the special cases "all" and "none" are recognized.
60   * If ommited, the value defaults to "all."
61   * Behavior is undefined if "all" or "none" are paired with additional values. Behavior is also
62   * undefined if values not listed above are included.
63   *
64   * Only those operations listed will occur between the returned WAL and HDFS. All others
65   * will be no-ops.
66   *
67   * Note that in the case of allowing "append" operations but not allowing "fileroll", the returned
68   * WAL will just keep writing to the same file. This won't avoid all costs associated with file
69   * management over time, becaue the data set size may result in additional HDFS block allocations.
70   *
71   */
72  @InterfaceAudience.Private
73  public class IOTestProvider implements WALProvider {
74    private static final Log LOG = LogFactory.getLog(IOTestProvider.class);
75  
76    private static final String ALLOWED_OPERATIONS = "hbase.wal.iotestprovider.operations";
77    private enum AllowedOperations {
78      all,
79      append,
80      sync,
81      fileroll,
82      none;
83    }
84  
85    private FSHLog log = null;
86  
87    /**
88     * @param factory factory that made us, identity used for FS layout. may not be null
89     * @param conf may not be null
90     * @param listeners may be null
91     * @param providerId differentiate between providers from one facotry, used for FS layout. may be
92     *                   null
93     */
94    @Override
95    public void init(final WALFactory factory, final Configuration conf,
96        final List<WALActionsListener> listeners, String providerId) throws IOException {
97      if (null != log) {
98        throw new IllegalStateException("WALProvider.init should only be called once.");
99      }
100     if (null == providerId) {
101       providerId = DEFAULT_PROVIDER_ID;
102     }
103     final String logPrefix = factory.factoryId + WAL_FILE_NAME_DELIMITER + providerId;
104     log = new IOTestWAL(CommonFSUtils.getWALFileSystem(conf), CommonFSUtils.getWALRootDir(conf),
105         DefaultWALProvider.getWALDirectoryName(factory.factoryId),
106         HConstants.HREGION_OLDLOGDIR_NAME, conf, listeners,
107         true, logPrefix, META_WAL_PROVIDER_ID.equals(providerId) ? META_WAL_PROVIDER_ID : null);
108   }
109 
110   @Override
111   public List<WAL> getWALs() throws IOException {
112     List<WAL> wals = new ArrayList<WAL>();
113     wals.add(log);
114     return wals;
115   }
116 
117   @Override
118   public WAL getWAL(final byte[] identifier, byte[] namespace) throws IOException {
119    return log;
120   }
121 
122   @Override
123   public void close() throws IOException {
124     log.close();
125   }
126 
127   @Override
128   public void shutdown() throws IOException {
129     log.shutdown();
130   }
131 
132   private static class IOTestWAL extends FSHLog {
133 
134     private final boolean doFileRolls;
135 
136     // Used to differntiate between roll calls before and after we finish construction.
137     private final boolean initialized;
138 
139     /**
140      * Create an edit log at the given <code>dir</code> location.
141      *
142      * You should never have to load an existing log. If there is a log at
143      * startup, it should have already been processed and deleted by the time the
144      * WAL object is started up.
145      *
146      * @param fs filesystem handle
147      * @param rootDir path to where logs and oldlogs
148      * @param logDir dir where wals are stored
149      * @param archiveDir dir where wals are archived
150      * @param conf configuration to use
151      * @param listeners Listeners on WAL events. Listeners passed here will
152      * be registered before we do anything else; e.g. the
153      * Constructor {@link #rollWriter()}.
154      * @param failIfWALExists If true IOException will be thrown if files related to this wal
155      *        already exist.
156      * @param prefix should always be hostname and port in distributed env and
157      *        it will be URL encoded before being used.
158      *        If prefix is null, "wal" will be used
159      * @param suffix will be url encoded. null is treated as empty. non-empty must start with
160      *        {@link DefaultWALProvider#WAL_FILE_NAME_DELIMITER}
161      * @throws IOException
162      */
163     public IOTestWAL(final FileSystem fs, final Path rootDir, final String logDir,
164         final String archiveDir, final Configuration conf,
165         final List<WALActionsListener> listeners,
166         final boolean failIfWALExists, final String prefix, final String suffix)
167         throws IOException {
168       super(fs, rootDir, logDir, archiveDir, conf, listeners, failIfWALExists, prefix, suffix);
169       Collection<String> operations = conf.getStringCollection(ALLOWED_OPERATIONS);
170       doFileRolls = operations.isEmpty() || operations.contains(AllowedOperations.all.name()) ||
171           operations.contains(AllowedOperations.fileroll.name());
172       initialized = true;
173       LOG.info("Initialized with file rolling " + (doFileRolls ? "enabled" : "disabled"));
174     }
175 
176     private Writer noRollsWriter;
177 
178     // creatWriterInstance is where the new pipeline is set up for doing file rolls
179     // if we are skipping it, just keep returning the same writer.
180     @Override
181     protected Writer createWriterInstance(final Path path) throws IOException {
182       // we get called from the FSHLog constructor (!); always roll in this case since
183       // we don't know yet if we're supposed to generally roll and
184       // we need an initial file in the case of doing appends but no rolls.
185       if (!initialized || doFileRolls) {
186         LOG.info("creating new writer instance.");
187         final ProtobufLogWriter writer = new IOTestWriter();
188         try {
189           writer.init(fs, path, conf, false);
190         } catch (CommonFSUtils.StreamLacksCapabilityException exception) {
191           throw new IOException("Can't create writer instance because underlying FileSystem " +
192               "doesn't support needed stream capabilities.", exception);
193         }
194         if (!initialized) {
195           LOG.info("storing initial writer instance in case file rolling isn't allowed.");
196           noRollsWriter = writer;
197         }
198         return writer;
199       } else {
200         LOG.info("WAL rolling disabled, returning the first writer.");
201         // Initial assignment happens during the constructor call, so there ought not be
202         // a race for first assignment.
203         return noRollsWriter;
204       }
205     }
206   }
207 
208   /**
209    * Presumes init will be called by a single thread prior to any access of other methods.
210    */
211   private static class IOTestWriter extends ProtobufLogWriter {
212     private boolean doAppends;
213     private boolean doSyncs;
214 
215     @Override
216     public void init(FileSystem fs, Path path, Configuration conf, boolean overwritable)
217         throws IOException, CommonFSUtils.StreamLacksCapabilityException {
218       Collection<String> operations = conf.getStringCollection(ALLOWED_OPERATIONS);
219       if (operations.isEmpty() || operations.contains(AllowedOperations.all.name())) {
220         doAppends = doSyncs = true;
221       } else if (operations.contains(AllowedOperations.none.name())) {
222         doAppends = doSyncs = false;
223       } else {
224         doAppends = operations.contains(AllowedOperations.append.name());
225         doSyncs = operations.contains(AllowedOperations.sync.name());
226       }
227       LOG.info("IOTestWriter initialized with appends " + (doAppends ? "enabled" : "disabled") +
228           " and syncs " + (doSyncs ? "enabled" : "disabled"));
229       super.init(fs, path, conf, overwritable);
230     }
231 
232     @Override
233     public void append(Entry entry) throws IOException {
234       if (doAppends) {
235         super.append(entry);
236       }
237     }
238 
239     @Override
240     public void sync(boolean forceSync) throws IOException {
241       if (doSyncs) {
242         super.sync(forceSync);
243       }
244     }
245   }
246 
247   @Override
248   public long getNumLogFiles() {
249     return this.log.getNumLogFiles();
250   }
251 
252   @Override
253   public long getLogFileSize() {
254     return this.log.getLogFileSize();
255   }
256 }