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