HADOOP-10750. KMSKeyProviderCache should be in hadoop-common. (asuresh via tucu)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/branch-2@1619536 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
f21bd86958
commit
7f8ac5b812
|
@ -151,6 +151,9 @@ Release 2.6.0 - UNRELEASED
|
||||||
HADOOP-10842. CryptoExtension generateEncryptedKey method should
|
HADOOP-10842. CryptoExtension generateEncryptedKey method should
|
||||||
receive the key name. (asuresh via tucu)
|
receive the key name. (asuresh via tucu)
|
||||||
|
|
||||||
|
HADOOP-10750. KMSKeyProviderCache should be in hadoop-common.
|
||||||
|
(asuresh via tucu)
|
||||||
|
|
||||||
BUG FIXES
|
BUG FIXES
|
||||||
|
|
||||||
HADOOP-10781. Unportable getgrouplist() usage breaks FreeBSD (Dmitry
|
HADOOP-10781. Unportable getgrouplist() usage breaks FreeBSD (Dmitry
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.hadoop.crypto.key;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <code>KeyProviderExtension</code> implementation providing a short lived
|
||||||
|
* cache for <code>KeyVersions</code> and <code>Metadata</code>to avoid burst
|
||||||
|
* of requests to hit the underlying <code>KeyProvider</code>.
|
||||||
|
*/
|
||||||
|
public class CachingKeyProvider extends
|
||||||
|
KeyProviderExtension<CachingKeyProvider.CacheExtension> {
|
||||||
|
|
||||||
|
static class CacheExtension implements KeyProviderExtension.Extension {
|
||||||
|
private final KeyProvider provider;
|
||||||
|
private LoadingCache<String, KeyVersion> keyVersionCache;
|
||||||
|
private LoadingCache<String, KeyVersion> currentKeyCache;
|
||||||
|
private LoadingCache<String, Metadata> keyMetadataCache;
|
||||||
|
|
||||||
|
CacheExtension(KeyProvider prov, long keyTimeoutMillis,
|
||||||
|
long currKeyTimeoutMillis) {
|
||||||
|
this.provider = prov;
|
||||||
|
keyVersionCache =
|
||||||
|
CacheBuilder.newBuilder().expireAfterAccess(keyTimeoutMillis,
|
||||||
|
TimeUnit.MILLISECONDS)
|
||||||
|
.build(new CacheLoader<String, KeyVersion>() {
|
||||||
|
@Override
|
||||||
|
public KeyVersion load(String key) throws Exception {
|
||||||
|
KeyVersion kv = provider.getKeyVersion(key);
|
||||||
|
if (kv == null) {
|
||||||
|
throw new KeyNotFoundException();
|
||||||
|
}
|
||||||
|
return kv;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
keyMetadataCache =
|
||||||
|
CacheBuilder.newBuilder().expireAfterAccess(keyTimeoutMillis,
|
||||||
|
TimeUnit.MILLISECONDS)
|
||||||
|
.build(new CacheLoader<String, Metadata>() {
|
||||||
|
@Override
|
||||||
|
public Metadata load(String key) throws Exception {
|
||||||
|
Metadata meta = provider.getMetadata(key);
|
||||||
|
if (meta == null) {
|
||||||
|
throw new KeyNotFoundException();
|
||||||
|
}
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentKeyCache =
|
||||||
|
CacheBuilder.newBuilder().expireAfterWrite(currKeyTimeoutMillis,
|
||||||
|
TimeUnit.MILLISECONDS)
|
||||||
|
.build(new CacheLoader<String, KeyVersion>() {
|
||||||
|
@Override
|
||||||
|
public KeyVersion load(String key) throws Exception {
|
||||||
|
KeyVersion kv = provider.getCurrentKey(key);
|
||||||
|
if (kv == null) {
|
||||||
|
throw new KeyNotFoundException();
|
||||||
|
}
|
||||||
|
return kv;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
private static class KeyNotFoundException extends Exception { }
|
||||||
|
|
||||||
|
public CachingKeyProvider(KeyProvider keyProvider, long keyTimeoutMillis,
|
||||||
|
long currKeyTimeoutMillis) {
|
||||||
|
super(keyProvider, new CacheExtension(keyProvider, keyTimeoutMillis,
|
||||||
|
currKeyTimeoutMillis));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyVersion getCurrentKey(String name) throws IOException {
|
||||||
|
try {
|
||||||
|
return getExtension().currentKeyCache.get(name);
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
Throwable cause = ex.getCause();
|
||||||
|
if (cause instanceof KeyNotFoundException) {
|
||||||
|
return null;
|
||||||
|
} else if (cause instanceof IOException) {
|
||||||
|
throw (IOException) cause;
|
||||||
|
} else {
|
||||||
|
throw new IOException(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyVersion getKeyVersion(String versionName)
|
||||||
|
throws IOException {
|
||||||
|
try {
|
||||||
|
return getExtension().keyVersionCache.get(versionName);
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
Throwable cause = ex.getCause();
|
||||||
|
if (cause instanceof KeyNotFoundException) {
|
||||||
|
return null;
|
||||||
|
} else if (cause instanceof IOException) {
|
||||||
|
throw (IOException) cause;
|
||||||
|
} else {
|
||||||
|
throw new IOException(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteKey(String name) throws IOException {
|
||||||
|
getKeyProvider().deleteKey(name);
|
||||||
|
getExtension().currentKeyCache.invalidate(name);
|
||||||
|
getExtension().keyMetadataCache.invalidate(name);
|
||||||
|
// invalidating all key versions as we don't know
|
||||||
|
// which ones belonged to the deleted key
|
||||||
|
getExtension().keyVersionCache.invalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyVersion rollNewVersion(String name, byte[] material)
|
||||||
|
throws IOException {
|
||||||
|
KeyVersion key = getKeyProvider().rollNewVersion(name, material);
|
||||||
|
getExtension().currentKeyCache.invalidate(name);
|
||||||
|
getExtension().keyMetadataCache.invalidate(name);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyVersion rollNewVersion(String name)
|
||||||
|
throws NoSuchAlgorithmException, IOException {
|
||||||
|
KeyVersion key = getKeyProvider().rollNewVersion(name);
|
||||||
|
getExtension().currentKeyCache.invalidate(name);
|
||||||
|
getExtension().keyMetadataCache.invalidate(name);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Metadata getMetadata(String name) throws IOException {
|
||||||
|
try {
|
||||||
|
return getExtension().keyMetadataCache.get(name);
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
Throwable cause = ex.getCause();
|
||||||
|
if (cause instanceof KeyNotFoundException) {
|
||||||
|
return null;
|
||||||
|
} else if (cause instanceof IOException) {
|
||||||
|
throw (IOException) cause;
|
||||||
|
} else {
|
||||||
|
throw new IOException(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,17 +15,16 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.hadoop.crypto.key.kms.server;
|
package org.apache.hadoop.crypto.key;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import org.apache.hadoop.crypto.key.KeyProvider;
|
|
||||||
import org.apache.hadoop.crypto.key.kms.KMSClientProvider;
|
import org.apache.hadoop.crypto.key.kms.KMSClientProvider;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.util.Date;
|
public class TestCachingKeyProvider {
|
||||||
|
|
||||||
public class TestKMSCacheKeyProvider {
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCurrentKey() throws Exception {
|
public void testCurrentKey() throws Exception {
|
||||||
|
@ -33,7 +32,7 @@ public class TestKMSCacheKeyProvider {
|
||||||
KeyProvider mockProv = Mockito.mock(KeyProvider.class);
|
KeyProvider mockProv = Mockito.mock(KeyProvider.class);
|
||||||
Mockito.when(mockProv.getCurrentKey(Mockito.eq("k1"))).thenReturn(mockKey);
|
Mockito.when(mockProv.getCurrentKey(Mockito.eq("k1"))).thenReturn(mockKey);
|
||||||
Mockito.when(mockProv.getCurrentKey(Mockito.eq("k2"))).thenReturn(null);
|
Mockito.when(mockProv.getCurrentKey(Mockito.eq("k2"))).thenReturn(null);
|
||||||
KeyProvider cache = new KMSCacheKeyProvider(mockProv, 100);
|
KeyProvider cache = new CachingKeyProvider(mockProv, 100, 100);
|
||||||
|
|
||||||
// asserting caching
|
// asserting caching
|
||||||
Assert.assertEquals(mockKey, cache.getCurrentKey("k1"));
|
Assert.assertEquals(mockKey, cache.getCurrentKey("k1"));
|
||||||
|
@ -45,7 +44,7 @@ public class TestKMSCacheKeyProvider {
|
||||||
Mockito.verify(mockProv, Mockito.times(2)).getCurrentKey(Mockito.eq("k1"));
|
Mockito.verify(mockProv, Mockito.times(2)).getCurrentKey(Mockito.eq("k1"));
|
||||||
|
|
||||||
// asserting no caching when key is not known
|
// asserting no caching when key is not known
|
||||||
cache = new KMSCacheKeyProvider(mockProv, 100);
|
cache = new CachingKeyProvider(mockProv, 100, 100);
|
||||||
Assert.assertEquals(null, cache.getCurrentKey("k2"));
|
Assert.assertEquals(null, cache.getCurrentKey("k2"));
|
||||||
Mockito.verify(mockProv, Mockito.times(1)).getCurrentKey(Mockito.eq("k2"));
|
Mockito.verify(mockProv, Mockito.times(1)).getCurrentKey(Mockito.eq("k2"));
|
||||||
Assert.assertEquals(null, cache.getCurrentKey("k2"));
|
Assert.assertEquals(null, cache.getCurrentKey("k2"));
|
||||||
|
@ -56,25 +55,56 @@ public class TestKMSCacheKeyProvider {
|
||||||
public void testKeyVersion() throws Exception {
|
public void testKeyVersion() throws Exception {
|
||||||
KeyProvider.KeyVersion mockKey = Mockito.mock(KeyProvider.KeyVersion.class);
|
KeyProvider.KeyVersion mockKey = Mockito.mock(KeyProvider.KeyVersion.class);
|
||||||
KeyProvider mockProv = Mockito.mock(KeyProvider.class);
|
KeyProvider mockProv = Mockito.mock(KeyProvider.class);
|
||||||
Mockito.when(mockProv.getKeyVersion(Mockito.eq("k1@0"))).thenReturn(mockKey);
|
Mockito.when(mockProv.getKeyVersion(Mockito.eq("k1@0")))
|
||||||
|
.thenReturn(mockKey);
|
||||||
Mockito.when(mockProv.getKeyVersion(Mockito.eq("k2@0"))).thenReturn(null);
|
Mockito.when(mockProv.getKeyVersion(Mockito.eq("k2@0"))).thenReturn(null);
|
||||||
KeyProvider cache = new KMSCacheKeyProvider(mockProv, 100);
|
KeyProvider cache = new CachingKeyProvider(mockProv, 100, 100);
|
||||||
|
|
||||||
// asserting caching
|
// asserting caching
|
||||||
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
||||||
Mockito.verify(mockProv, Mockito.times(1)).getKeyVersion(Mockito.eq("k1@0"));
|
Mockito.verify(mockProv, Mockito.times(1))
|
||||||
|
.getKeyVersion(Mockito.eq("k1@0"));
|
||||||
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
||||||
Mockito.verify(mockProv, Mockito.times(1)).getKeyVersion(Mockito.eq("k1@0"));
|
Mockito.verify(mockProv, Mockito.times(1))
|
||||||
|
.getKeyVersion(Mockito.eq("k1@0"));
|
||||||
Thread.sleep(200);
|
Thread.sleep(200);
|
||||||
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
||||||
Mockito.verify(mockProv, Mockito.times(2)).getKeyVersion(Mockito.eq("k1@0"));
|
Mockito.verify(mockProv, Mockito.times(2))
|
||||||
|
.getKeyVersion(Mockito.eq("k1@0"));
|
||||||
|
|
||||||
// asserting no caching when key is not known
|
// asserting no caching when key is not known
|
||||||
cache = new KMSCacheKeyProvider(mockProv, 100);
|
cache = new CachingKeyProvider(mockProv, 100, 100);
|
||||||
Assert.assertEquals(null, cache.getKeyVersion("k2@0"));
|
Assert.assertEquals(null, cache.getKeyVersion("k2@0"));
|
||||||
Mockito.verify(mockProv, Mockito.times(1)).getKeyVersion(Mockito.eq("k2@0"));
|
Mockito.verify(mockProv, Mockito.times(1))
|
||||||
|
.getKeyVersion(Mockito.eq("k2@0"));
|
||||||
Assert.assertEquals(null, cache.getKeyVersion("k2@0"));
|
Assert.assertEquals(null, cache.getKeyVersion("k2@0"));
|
||||||
Mockito.verify(mockProv, Mockito.times(2)).getKeyVersion(Mockito.eq("k2@0"));
|
Mockito.verify(mockProv, Mockito.times(2))
|
||||||
|
.getKeyVersion(Mockito.eq("k2@0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMetadata() throws Exception {
|
||||||
|
KeyProvider.Metadata mockMeta = Mockito.mock(KeyProvider.Metadata.class);
|
||||||
|
KeyProvider mockProv = Mockito.mock(KeyProvider.class);
|
||||||
|
Mockito.when(mockProv.getMetadata(Mockito.eq("k1"))).thenReturn(mockMeta);
|
||||||
|
Mockito.when(mockProv.getMetadata(Mockito.eq("k2"))).thenReturn(null);
|
||||||
|
KeyProvider cache = new CachingKeyProvider(mockProv, 100, 100);
|
||||||
|
|
||||||
|
// asserting caching
|
||||||
|
Assert.assertEquals(mockMeta, cache.getMetadata("k1"));
|
||||||
|
Mockito.verify(mockProv, Mockito.times(1)).getMetadata(Mockito.eq("k1"));
|
||||||
|
Assert.assertEquals(mockMeta, cache.getMetadata("k1"));
|
||||||
|
Mockito.verify(mockProv, Mockito.times(1)).getMetadata(Mockito.eq("k1"));
|
||||||
|
Thread.sleep(200);
|
||||||
|
Assert.assertEquals(mockMeta, cache.getMetadata("k1"));
|
||||||
|
Mockito.verify(mockProv, Mockito.times(2)).getMetadata(Mockito.eq("k1"));
|
||||||
|
|
||||||
|
// asserting no caching when key is not known
|
||||||
|
cache = new CachingKeyProvider(mockProv, 100, 100);
|
||||||
|
Assert.assertEquals(null, cache.getMetadata("k2"));
|
||||||
|
Mockito.verify(mockProv, Mockito.times(1)).getMetadata(Mockito.eq("k2"));
|
||||||
|
Assert.assertEquals(null, cache.getMetadata("k2"));
|
||||||
|
Mockito.verify(mockProv, Mockito.times(2)).getMetadata(Mockito.eq("k2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -82,7 +112,7 @@ public class TestKMSCacheKeyProvider {
|
||||||
KeyProvider.KeyVersion mockKey = Mockito.mock(KeyProvider.KeyVersion.class);
|
KeyProvider.KeyVersion mockKey = Mockito.mock(KeyProvider.KeyVersion.class);
|
||||||
KeyProvider mockProv = Mockito.mock(KeyProvider.class);
|
KeyProvider mockProv = Mockito.mock(KeyProvider.class);
|
||||||
Mockito.when(mockProv.getCurrentKey(Mockito.eq("k1"))).thenReturn(mockKey);
|
Mockito.when(mockProv.getCurrentKey(Mockito.eq("k1"))).thenReturn(mockKey);
|
||||||
KeyProvider cache = new KMSCacheKeyProvider(mockProv, 100);
|
KeyProvider cache = new CachingKeyProvider(mockProv, 100, 100);
|
||||||
Assert.assertEquals(mockKey, cache.getCurrentKey("k1"));
|
Assert.assertEquals(mockKey, cache.getCurrentKey("k1"));
|
||||||
Mockito.verify(mockProv, Mockito.times(1)).getCurrentKey(Mockito.eq("k1"));
|
Mockito.verify(mockProv, Mockito.times(1)).getCurrentKey(Mockito.eq("k1"));
|
||||||
cache.rollNewVersion("k1");
|
cache.rollNewVersion("k1");
|
||||||
|
@ -100,21 +130,23 @@ public class TestKMSCacheKeyProvider {
|
||||||
KeyProvider.KeyVersion mockKey = Mockito.mock(KeyProvider.KeyVersion.class);
|
KeyProvider.KeyVersion mockKey = Mockito.mock(KeyProvider.KeyVersion.class);
|
||||||
KeyProvider mockProv = Mockito.mock(KeyProvider.class);
|
KeyProvider mockProv = Mockito.mock(KeyProvider.class);
|
||||||
Mockito.when(mockProv.getCurrentKey(Mockito.eq("k1"))).thenReturn(mockKey);
|
Mockito.when(mockProv.getCurrentKey(Mockito.eq("k1"))).thenReturn(mockKey);
|
||||||
Mockito.when(mockProv.getKeyVersion(Mockito.eq("k1@0"))).thenReturn(mockKey);
|
Mockito.when(mockProv.getKeyVersion(Mockito.eq("k1@0")))
|
||||||
|
.thenReturn(mockKey);
|
||||||
Mockito.when(mockProv.getMetadata(Mockito.eq("k1"))).thenReturn(
|
Mockito.when(mockProv.getMetadata(Mockito.eq("k1"))).thenReturn(
|
||||||
new KMSClientProvider.KMSMetadata("c", 0, "l", null, new Date(), 1));
|
new KMSClientProvider.KMSMetadata("c", 0, "l", null, new Date(), 1));
|
||||||
KeyProvider cache = new KMSCacheKeyProvider(mockProv, 100);
|
KeyProvider cache = new CachingKeyProvider(mockProv, 100, 100);
|
||||||
Assert.assertEquals(mockKey, cache.getCurrentKey("k1"));
|
Assert.assertEquals(mockKey, cache.getCurrentKey("k1"));
|
||||||
Mockito.verify(mockProv, Mockito.times(1)).getCurrentKey(Mockito.eq("k1"));
|
Mockito.verify(mockProv, Mockito.times(1)).getCurrentKey(Mockito.eq("k1"));
|
||||||
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
||||||
Mockito.verify(mockProv, Mockito.times(1)).getKeyVersion(Mockito.eq("k1@0"));
|
Mockito.verify(mockProv, Mockito.times(1))
|
||||||
|
.getKeyVersion(Mockito.eq("k1@0"));
|
||||||
cache.deleteKey("k1");
|
cache.deleteKey("k1");
|
||||||
|
|
||||||
// asserting the cache is purged
|
// asserting the cache is purged
|
||||||
Assert.assertEquals(mockKey, cache.getCurrentKey("k1"));
|
Assert.assertEquals(mockKey, cache.getCurrentKey("k1"));
|
||||||
Mockito.verify(mockProv, Mockito.times(2)).getCurrentKey(Mockito.eq("k1"));
|
Mockito.verify(mockProv, Mockito.times(2)).getCurrentKey(Mockito.eq("k1"));
|
||||||
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
Assert.assertEquals(mockKey, cache.getKeyVersion("k1@0"));
|
||||||
Mockito.verify(mockProv, Mockito.times(2)).getKeyVersion(Mockito.eq("k1@0"));
|
Mockito.verify(mockProv, Mockito.times(2))
|
||||||
|
.getKeyVersion(Mockito.eq("k1@0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,177 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.hadoop.crypto.key.kms.server;
|
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import org.apache.hadoop.crypto.key.KeyProvider;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A <code>KeyProvider</code> proxy implementation providing a short lived
|
|
||||||
* cache for <code>KeyVersions</code> to avoid burst of requests to hit the
|
|
||||||
* underlying <code>KeyProvider</code>.
|
|
||||||
*/
|
|
||||||
public class KMSCacheKeyProvider extends KeyProvider {
|
|
||||||
private final KeyProvider provider;
|
|
||||||
private LoadingCache<String, KeyVersion> keyVersionCache;
|
|
||||||
private LoadingCache<String, KeyVersion> currentKeyCache;
|
|
||||||
|
|
||||||
private static class KeyNotFoundException extends Exception {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KMSCacheKeyProvider(KeyProvider prov, long timeoutMillis) {
|
|
||||||
this.provider = prov;
|
|
||||||
keyVersionCache = CacheBuilder.newBuilder().expireAfterAccess(timeoutMillis,
|
|
||||||
TimeUnit.MILLISECONDS).build(new CacheLoader<String, KeyVersion>() {
|
|
||||||
@Override
|
|
||||||
public KeyVersion load(String key) throws Exception {
|
|
||||||
KeyVersion kv = provider.getKeyVersion(key);
|
|
||||||
if (kv == null) {
|
|
||||||
throw new KeyNotFoundException();
|
|
||||||
}
|
|
||||||
return kv;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// for current key we don't want to go stale for more than 1 sec
|
|
||||||
currentKeyCache = CacheBuilder.newBuilder().expireAfterWrite(1000,
|
|
||||||
TimeUnit.MILLISECONDS).build(new CacheLoader<String, KeyVersion>() {
|
|
||||||
@Override
|
|
||||||
public KeyVersion load(String key) throws Exception {
|
|
||||||
KeyVersion kv = provider.getCurrentKey(key);
|
|
||||||
if (kv == null) {
|
|
||||||
throw new KeyNotFoundException();
|
|
||||||
}
|
|
||||||
return kv;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyVersion getCurrentKey(String name) throws IOException {
|
|
||||||
try {
|
|
||||||
return currentKeyCache.get(name);
|
|
||||||
} catch (ExecutionException ex) {
|
|
||||||
Throwable cause = ex.getCause();
|
|
||||||
if (cause instanceof KeyNotFoundException) {
|
|
||||||
return null;
|
|
||||||
} else if (cause instanceof IOException) {
|
|
||||||
throw (IOException) cause;
|
|
||||||
} else {
|
|
||||||
throw new IOException(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyVersion getKeyVersion(String versionName)
|
|
||||||
throws IOException {
|
|
||||||
try {
|
|
||||||
return keyVersionCache.get(versionName);
|
|
||||||
} catch (ExecutionException ex) {
|
|
||||||
Throwable cause = ex.getCause();
|
|
||||||
if (cause instanceof KeyNotFoundException) {
|
|
||||||
return null;
|
|
||||||
} else if (cause instanceof IOException) {
|
|
||||||
throw (IOException) cause;
|
|
||||||
} else {
|
|
||||||
throw new IOException(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getKeys() throws IOException {
|
|
||||||
return provider.getKeys();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<KeyVersion> getKeyVersions(String name)
|
|
||||||
throws IOException {
|
|
||||||
return provider.getKeyVersions(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Metadata getMetadata(String name) throws IOException {
|
|
||||||
return provider.getMetadata(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyVersion createKey(String name, byte[] material,
|
|
||||||
Options options) throws IOException {
|
|
||||||
return provider.createKey(name, material, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyVersion createKey(String name,
|
|
||||||
Options options)
|
|
||||||
throws NoSuchAlgorithmException, IOException {
|
|
||||||
return provider.createKey(name, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteKey(String name) throws IOException {
|
|
||||||
provider.deleteKey(name);
|
|
||||||
currentKeyCache.invalidate(name);
|
|
||||||
// invalidating all key versions as we don't know which ones belonged to the
|
|
||||||
// deleted key
|
|
||||||
keyVersionCache.invalidateAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyVersion rollNewVersion(String name, byte[] material)
|
|
||||||
throws IOException {
|
|
||||||
KeyVersion key = provider.rollNewVersion(name, material);
|
|
||||||
currentKeyCache.invalidate(name);
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyVersion rollNewVersion(String name)
|
|
||||||
throws NoSuchAlgorithmException, IOException {
|
|
||||||
KeyVersion key = provider.rollNewVersion(name);
|
|
||||||
currentKeyCache.invalidate(name);
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() throws IOException {
|
|
||||||
provider.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Metadata[] getKeysMetadata(String ... keyNames)
|
|
||||||
throws IOException {
|
|
||||||
return provider.getKeysMetadata(keyNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTransient() {
|
|
||||||
return provider.isTransient();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -34,9 +34,21 @@ public class KMSConfiguration {
|
||||||
|
|
||||||
public static final String CONFIG_PREFIX = "hadoop.kms.";
|
public static final String CONFIG_PREFIX = "hadoop.kms.";
|
||||||
|
|
||||||
|
// Property to Enable/Disable Caching
|
||||||
|
public static final String KEY_CACHE_ENABLE = CONFIG_PREFIX +
|
||||||
|
"cache.enable";
|
||||||
|
// Timeout for the Key and Metadata Cache
|
||||||
public static final String KEY_CACHE_TIMEOUT_KEY = CONFIG_PREFIX +
|
public static final String KEY_CACHE_TIMEOUT_KEY = CONFIG_PREFIX +
|
||||||
"cache.timeout.ms";
|
"cache.timeout.ms";
|
||||||
public static final long KEY_CACHE_TIMEOUT_DEFAULT = 10 * 1000; // 10 secs
|
// TImeout for the Current Key cache
|
||||||
|
public static final String CURR_KEY_CACHE_TIMEOUT_KEY = CONFIG_PREFIX +
|
||||||
|
"current.key.cache.timeout.ms";
|
||||||
|
|
||||||
|
public static final boolean KEY_CACHE_ENABLE_DEFAULT = true;
|
||||||
|
// 10 mins
|
||||||
|
public static final long KEY_CACHE_TIMEOUT_DEFAULT = 10 * 60 * 1000;
|
||||||
|
// 30 secs
|
||||||
|
public static final long CURR_KEY_CACHE_TIMEOUT_DEFAULT = 30 * 1000;
|
||||||
|
|
||||||
static Configuration getConfiguration(boolean loadHadoopDefaults,
|
static Configuration getConfiguration(boolean loadHadoopDefaults,
|
||||||
String ... resources) {
|
String ... resources) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import com.codahale.metrics.Meter;
|
||||||
import com.codahale.metrics.MetricRegistry;
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.crypto.key.CachingKeyProvider;
|
||||||
import org.apache.hadoop.crypto.key.KeyProvider;
|
import org.apache.hadoop.crypto.key.KeyProvider;
|
||||||
import org.apache.hadoop.crypto.key.KeyProviderFactory;
|
import org.apache.hadoop.crypto.key.KeyProviderFactory;
|
||||||
import org.apache.hadoop.http.HttpServer2;
|
import org.apache.hadoop.http.HttpServer2;
|
||||||
|
@ -150,10 +151,17 @@ public class KMSWebApp implements ServletContextListener {
|
||||||
kmsConf.get(KeyProviderFactory.KEY_PROVIDER_PATH));
|
kmsConf.get(KeyProviderFactory.KEY_PROVIDER_PATH));
|
||||||
}
|
}
|
||||||
keyProvider = providers.get(0);
|
keyProvider = providers.get(0);
|
||||||
long timeOutMillis =
|
if (kmsConf.getBoolean(KMSConfiguration.KEY_CACHE_ENABLE,
|
||||||
kmsConf.getLong(KMSConfiguration.KEY_CACHE_TIMEOUT_KEY,
|
KMSConfiguration.KEY_CACHE_ENABLE_DEFAULT)) {
|
||||||
KMSConfiguration.KEY_CACHE_TIMEOUT_DEFAULT);
|
long keyTimeOutMillis =
|
||||||
keyProvider = new KMSCacheKeyProvider(keyProvider, timeOutMillis);
|
kmsConf.getLong(KMSConfiguration.KEY_CACHE_TIMEOUT_KEY,
|
||||||
|
KMSConfiguration.KEY_CACHE_TIMEOUT_DEFAULT);
|
||||||
|
long currKeyTimeOutMillis =
|
||||||
|
kmsConf.getLong(KMSConfiguration.CURR_KEY_CACHE_TIMEOUT_KEY,
|
||||||
|
KMSConfiguration.CURR_KEY_CACHE_TIMEOUT_DEFAULT);
|
||||||
|
keyProvider = new CachingKeyProvider(keyProvider, keyTimeOutMillis,
|
||||||
|
currKeyTimeOutMillis);
|
||||||
|
}
|
||||||
|
|
||||||
LOG.info("KMS Started");
|
LOG.info("KMS Started");
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
|
|
|
@ -72,22 +72,35 @@ Hadoop Key Management Server (KMS) - Documentation Sets ${project.version}
|
||||||
KMS caches keys for short period of time to avoid excessive hits to the
|
KMS caches keys for short period of time to avoid excessive hits to the
|
||||||
underlying key provider.
|
underlying key provider.
|
||||||
|
|
||||||
The cache is used with the following 2 methods only, <<<getCurrentKey()>>>
|
The Cache is enabled by default (can be dissabled by setting the
|
||||||
and <<<getKeyVersion()>>>.
|
<<<hadoop.kms.cache.enable>>> boolean property to false)
|
||||||
|
|
||||||
|
The cache is used with the following 3 methods only, <<<getCurrentKey()>>>
|
||||||
|
and <<<getKeyVersion()>>> and <<<getMetadata()>>>.
|
||||||
|
|
||||||
For the <<<getCurrentKey()>>> method, cached entries are kept for a maximum
|
For the <<<getCurrentKey()>>> method, cached entries are kept for a maximum
|
||||||
of 1000 millisecond regardless the number of times the key is being access
|
of 30000 millisecond regardless the number of times the key is being access
|
||||||
(to avoid stale keys to be considered current).
|
(to avoid stale keys to be considered current).
|
||||||
|
|
||||||
For the <<<getKeyVersion()>>> method, cached entries are kept with a default
|
For the <<<getKeyVersion()>>> method, cached entries are kept with a default
|
||||||
inactivity timeout of 10000 milliseconds. This time out is configurable via
|
inactivity timeout of 600000 milliseconds (10 mins). This time out is
|
||||||
the following property in the <<<etc/hadoop/kms-site.xml>>> configuration
|
configurable via the following property in the <<<etc/hadoop/kms-site.xml>>>
|
||||||
file:
|
configuration file:
|
||||||
|
|
||||||
+---+
|
+---+
|
||||||
|
<property>
|
||||||
|
<name>hadoop.kms.cache.enable</name>
|
||||||
|
<value>true</value>
|
||||||
|
</property>
|
||||||
|
|
||||||
<property>
|
<property>
|
||||||
<name>hadoop.kms.cache.timeout.ms</name>
|
<name>hadoop.kms.cache.timeout.ms</name>
|
||||||
<value>10000</value>
|
<value>600000</value>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<name>hadoop.kms.current.key.cache.timeout.ms</name>
|
||||||
|
<value>30000</value>
|
||||||
</property>
|
</property>
|
||||||
+---+
|
+---+
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue