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.wal; 019 020import static org.apache.hadoop.hbase.regionserver.wal.ProtobufLogReader.DEFAULT_WAL_TRAILER_WARN_SIZE; 021import static org.apache.hadoop.hbase.regionserver.wal.ProtobufLogReader.WAL_TRAILER_WARN_SIZE; 022 023import java.io.IOException; 024import java.io.OutputStream; 025import java.security.Key; 026import java.security.SecureRandom; 027import java.util.concurrent.atomic.AtomicLong; 028 029import javax.crypto.spec.SecretKeySpec; 030 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.fs.FileSystem; 033import org.apache.hadoop.fs.Path; 034import org.apache.hadoop.hbase.HBaseInterfaceAudience; 035import org.apache.hadoop.hbase.HConstants; 036import org.apache.yetus.audience.InterfaceAudience; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039import org.apache.hadoop.hbase.codec.Codec; 040import org.apache.hadoop.hbase.io.crypto.Cipher; 041import org.apache.hadoop.hbase.io.crypto.Encryption; 042import org.apache.hadoop.hbase.io.crypto.Encryptor; 043import org.apache.hadoop.hbase.io.util.LRUDictionary; 044import org.apache.hadoop.hbase.security.EncryptionUtil; 045import org.apache.hadoop.hbase.security.User; 046import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations; 047import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.WALHeader; 048import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.WALTrailer; 049import org.apache.hadoop.hbase.util.CommonFSUtils.StreamLacksCapabilityException; 050import org.apache.hadoop.hbase.util.EncryptionTest; 051import org.apache.hadoop.hbase.util.FSUtils; 052 053/** 054 * Base class for Protobuf log writer. 055 */ 056@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) 057public abstract class AbstractProtobufLogWriter { 058 059 private static final Logger LOG = LoggerFactory.getLogger(AbstractProtobufLogWriter.class); 060 061 protected CompressionContext compressionContext; 062 protected Configuration conf; 063 protected Codec.Encoder cellEncoder; 064 protected WALCellCodec.ByteStringCompressor compressor; 065 protected boolean trailerWritten; 066 protected WALTrailer trailer; 067 // maximum size of the wal Trailer in bytes. If a user writes/reads a trailer with size larger 068 // than this size, it is written/read respectively, with a WARN message in the log. 069 protected int trailerWarnSize; 070 071 protected AtomicLong length = new AtomicLong(); 072 073 private WALCellCodec getCodec(Configuration conf, CompressionContext compressionContext) 074 throws IOException { 075 return WALCellCodec.create(conf, null, compressionContext); 076 } 077 078 private WALHeader buildWALHeader0(Configuration conf, WALHeader.Builder builder) { 079 if (!builder.hasWriterClsName()) { 080 builder.setWriterClsName(getWriterClassName()); 081 } 082 if (!builder.hasCellCodecClsName()) { 083 builder.setCellCodecClsName(WALCellCodec.getWALCellCodecClass(conf)); 084 } 085 return builder.build(); 086 } 087 088 protected WALHeader buildWALHeader(Configuration conf, WALHeader.Builder builder) 089 throws IOException { 090 return buildWALHeader0(conf, builder); 091 } 092 093 // should be called in sub classes's buildWALHeader method to build WALHeader for secure 094 // environment. Do not forget to override the setEncryptor method as it will be called in this 095 // method to init your encryptor. 096 protected final WALHeader buildSecureWALHeader(Configuration conf, WALHeader.Builder builder) 097 throws IOException { 098 builder.setWriterClsName(getWriterClassName()); 099 if (conf.getBoolean(HConstants.ENABLE_WAL_ENCRYPTION, false)) { 100 EncryptionTest.testKeyProvider(conf); 101 EncryptionTest.testCipherProvider(conf); 102 103 // Get an instance of our cipher 104 final String cipherName = 105 conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); 106 Cipher cipher = Encryption.getCipher(conf, cipherName); 107 if (cipher == null) { 108 throw new RuntimeException("Cipher '" + cipherName + "' is not available"); 109 } 110 111 // Generate an encryption key for this WAL 112 SecureRandom rng = new SecureRandom(); 113 byte[] keyBytes = new byte[cipher.getKeyLength()]; 114 rng.nextBytes(keyBytes); 115 Key key = new SecretKeySpec(keyBytes, cipher.getName()); 116 builder.setEncryptionKey(UnsafeByteOperations.unsafeWrap(EncryptionUtil.wrapKey(conf, 117 conf.get(HConstants.CRYPTO_WAL_KEY_NAME_CONF_KEY, 118 conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, 119 User.getCurrent().getShortName())), 120 key))); 121 122 // Set up the encryptor 123 Encryptor encryptor = cipher.getEncryptor(); 124 encryptor.setKey(key); 125 setEncryptor(encryptor); 126 if (LOG.isTraceEnabled()) { 127 LOG.trace("Initialized secure protobuf WAL: cipher=" + cipher.getName()); 128 } 129 } 130 builder.setCellCodecClsName(SecureWALCellCodec.class.getName()); 131 return buildWALHeader0(conf, builder); 132 } 133 134 // override this if you need a encryptor 135 protected void setEncryptor(Encryptor encryptor) { 136 } 137 138 protected String getWriterClassName() { 139 return getClass().getSimpleName(); 140 } 141 142 private boolean initializeCompressionContext(Configuration conf, Path path) throws IOException { 143 boolean doCompress = conf.getBoolean(HConstants.ENABLE_WAL_COMPRESSION, false); 144 if (doCompress) { 145 try { 146 this.compressionContext = new CompressionContext(LRUDictionary.class, 147 FSUtils.isRecoveredEdits(path), 148 conf.getBoolean(CompressionContext.ENABLE_WAL_TAGS_COMPRESSION, true)); 149 } catch (Exception e) { 150 throw new IOException("Failed to initiate CompressionContext", e); 151 } 152 } 153 return doCompress; 154 } 155 156 public void init(FileSystem fs, Path path, Configuration conf, boolean overwritable, 157 long blocksize) throws IOException, StreamLacksCapabilityException { 158 this.conf = conf; 159 boolean doCompress = initializeCompressionContext(conf, path); 160 this.trailerWarnSize = conf.getInt(WAL_TRAILER_WARN_SIZE, DEFAULT_WAL_TRAILER_WARN_SIZE); 161 int bufferSize = FSUtils.getDefaultBufferSize(fs); 162 short replication = (short) conf.getInt("hbase.regionserver.hlog.replication", 163 FSUtils.getDefaultReplication(fs, path)); 164 165 initOutput(fs, path, overwritable, bufferSize, replication, blocksize); 166 167 boolean doTagCompress = doCompress 168 && conf.getBoolean(CompressionContext.ENABLE_WAL_TAGS_COMPRESSION, true); 169 length.set(writeMagicAndWALHeader(ProtobufLogReader.PB_WAL_MAGIC, buildWALHeader(conf, 170 WALHeader.newBuilder().setHasCompression(doCompress).setHasTagCompression(doTagCompress)))); 171 172 initAfterHeader(doCompress); 173 174 // instantiate trailer to default value. 175 trailer = WALTrailer.newBuilder().build(); 176 if (LOG.isTraceEnabled()) { 177 LOG.trace("Initialized protobuf WAL=" + path + ", compression=" + doCompress); 178 } 179 } 180 181 private void initAfterHeader0(boolean doCompress) throws IOException { 182 WALCellCodec codec = getCodec(conf, this.compressionContext); 183 this.cellEncoder = codec.getEncoder(getOutputStreamForCellEncoder()); 184 if (doCompress) { 185 this.compressor = codec.getByteStringCompressor(); 186 } 187 } 188 189 protected void initAfterHeader(boolean doCompress) throws IOException { 190 initAfterHeader0(doCompress); 191 } 192 193 // should be called in sub classes's initAfterHeader method to init SecureWALCellCodec. 194 protected final void secureInitAfterHeader(boolean doCompress, Encryptor encryptor) 195 throws IOException { 196 if (conf.getBoolean(HConstants.ENABLE_WAL_ENCRYPTION, false) && encryptor != null) { 197 WALCellCodec codec = SecureWALCellCodec.getCodec(this.conf, encryptor); 198 this.cellEncoder = codec.getEncoder(getOutputStreamForCellEncoder()); 199 // We do not support compression 200 this.compressionContext = null; 201 } else { 202 initAfterHeader0(doCompress); 203 } 204 } 205 206 void setWALTrailer(WALTrailer walTrailer) { 207 this.trailer = walTrailer; 208 } 209 210 public long getLength() { 211 return length.get(); 212 } 213 214 private WALTrailer buildWALTrailer(WALTrailer.Builder builder) { 215 return builder.build(); 216 } 217 218 protected void writeWALTrailer() { 219 try { 220 int trailerSize = 0; 221 if (this.trailer == null) { 222 // use default trailer. 223 LOG.warn("WALTrailer is null. Continuing with default."); 224 this.trailer = buildWALTrailer(WALTrailer.newBuilder()); 225 trailerSize = this.trailer.getSerializedSize(); 226 } else if ((trailerSize = this.trailer.getSerializedSize()) > this.trailerWarnSize) { 227 // continue writing after warning the user. 228 LOG.warn("Please investigate WALTrailer usage. Trailer size > maximum size : " + trailerSize 229 + " > " + this.trailerWarnSize); 230 } 231 length.set(writeWALTrailerAndMagic(trailer, ProtobufLogReader.PB_WAL_COMPLETE_MAGIC)); 232 this.trailerWritten = true; 233 } catch (IOException ioe) { 234 LOG.warn("Failed to write trailer, non-fatal, continuing...", ioe); 235 } 236 } 237 238 protected abstract void initOutput(FileSystem fs, Path path, boolean overwritable, int bufferSize, 239 short replication, long blockSize) throws IOException, StreamLacksCapabilityException; 240 241 /** 242 * return the file length after written. 243 */ 244 protected abstract long writeMagicAndWALHeader(byte[] magic, WALHeader header) throws IOException; 245 246 protected abstract long writeWALTrailerAndMagic(WALTrailer trailer, byte[] magic) 247 throws IOException; 248 249 protected abstract OutputStream getOutputStreamForCellEncoder(); 250}