/*
* 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.flink.runtime.blob;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.security.MessageDigest;
import java.util.Collections;
import java.util.List;
import org.apache.flink.configuration.BlobServerOptions;
import org.apache.flink.configuration.ConfigConstants;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.api.common.JobID;
import org.apache.flink.core.fs.Path;
import org.apache.flink.util.TestLogger;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* This class contains unit tests for the {@link BlobClient} with ssl enabled.
*/
public class BlobClientSslTest extends TestLogger {
/** The buffer size used during the tests in bytes. */
private static final int TEST_BUFFER_SIZE = 17 * 1000;
/** The instance of the SSL BLOB server used during the tests. */
private static BlobServer BLOB_SSL_SERVER;
/** The SSL blob service client configuration */
private static Configuration sslClientConfig;
/** The instance of the non-SSL BLOB server used during the tests. */
private static BlobServer BLOB_SERVER;
/** The non-ssl blob service client configuration */
private static Configuration clientConfig;
/**
* Starts the SSL enabled BLOB server.
*/
@BeforeClass
public static void startSSLServer() throws IOException {
Configuration config = new Configuration();
config.setBoolean(ConfigConstants.SECURITY_SSL_ENABLED, true);
config.setString(ConfigConstants.SECURITY_SSL_KEYSTORE, "src/test/resources/local127.keystore");
config.setString(ConfigConstants.SECURITY_SSL_KEYSTORE_PASSWORD, "password");
config.setString(ConfigConstants.SECURITY_SSL_KEY_PASSWORD, "password");
BLOB_SSL_SERVER = new BlobServer(config, new VoidBlobStore());
sslClientConfig = new Configuration();
sslClientConfig.setBoolean(ConfigConstants.SECURITY_SSL_ENABLED, true);
sslClientConfig.setString(ConfigConstants.SECURITY_SSL_TRUSTSTORE, "src/test/resources/local127.truststore");
sslClientConfig.setString(ConfigConstants.SECURITY_SSL_TRUSTSTORE_PASSWORD, "password");
}
/**
* Starts the SSL disabled BLOB server.
*/
@BeforeClass
public static void startNonSSLServer() throws IOException {
Configuration config = new Configuration();
config.setBoolean(ConfigConstants.SECURITY_SSL_ENABLED, true);
config.setBoolean(BlobServerOptions.SSL_ENABLED, false);
config.setString(ConfigConstants.SECURITY_SSL_KEYSTORE, "src/test/resources/local127.keystore");
config.setString(ConfigConstants.SECURITY_SSL_KEYSTORE_PASSWORD, "password");
config.setString(ConfigConstants.SECURITY_SSL_KEY_PASSWORD, "password");
BLOB_SERVER = new BlobServer(config, new VoidBlobStore());
clientConfig = new Configuration();
clientConfig.setBoolean(ConfigConstants.SECURITY_SSL_ENABLED, true);
clientConfig.setBoolean(BlobServerOptions.SSL_ENABLED, false);
clientConfig.setString(ConfigConstants.SECURITY_SSL_TRUSTSTORE, "src/test/resources/local127.truststore");
clientConfig.setString(ConfigConstants.SECURITY_SSL_TRUSTSTORE_PASSWORD, "password");
}
/**
* Shuts the BLOB server down.
*/
@AfterClass
public static void stopServers() throws IOException {
if (BLOB_SSL_SERVER != null) {
BLOB_SSL_SERVER.close();
}
if (BLOB_SERVER != null) {
BLOB_SERVER.close();
}
}
/**
* Prepares a test file for the unit tests, i.e. the methods fills the file with a particular byte patterns and
* computes the file's BLOB key.
*
* @param file
* the file to prepare for the unit tests
* @return the BLOB key of the prepared file
* @throws IOException
* thrown if an I/O error occurs while writing to the test file
*/
private static BlobKey prepareTestFile(File file) throws IOException {
MessageDigest md = BlobUtils.createMessageDigest();
final byte[] buf = new byte[TEST_BUFFER_SIZE];
for (int i = 0; i < buf.length; ++i) {
buf[i] = (byte) (i % 128);
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
for (int i = 0; i < 20; ++i) {
fos.write(buf);
md.update(buf);
}
} finally {
if (fos != null) {
fos.close();
}
}
return new BlobKey(md.digest());
}
/**
* Validates the result of a GET operation by comparing the data from the retrieved input stream to the content of
* the specified file.
*
* @param inputStream
* the input stream returned from the GET operation
* @param file
* the file to compare the input stream's data to
* @throws IOException
* thrown if an I/O error occurs while reading the input stream or the file
*/
private static void validateGet(final InputStream inputStream, final File file) throws IOException {
InputStream inputStream2 = null;
try {
inputStream2 = new FileInputStream(file);
while (true) {
final int r1 = inputStream.read();
final int r2 = inputStream2.read();
assertEquals(r2, r1);
if (r1 < 0) {
break;
}
}
} finally {
if (inputStream2 != null) {
inputStream2.close();
}
}
}
/**
* Tests the PUT/GET operations for content-addressable streams.
*/
@Test
public void testContentAddressableStream() {
BlobClient client = null;
InputStream is = null;
try {
File testFile = File.createTempFile("testfile", ".dat");
testFile.deleteOnExit();
BlobKey origKey = prepareTestFile(testFile);
InetSocketAddress serverAddress = new InetSocketAddress("localhost", BLOB_SSL_SERVER.getPort());
client = new BlobClient(serverAddress, sslClientConfig);
// Store the data
is = new FileInputStream(testFile);
BlobKey receivedKey = client.put(is);
assertEquals(origKey, receivedKey);
is.close();
is = null;
// Retrieve the data
is = client.get(receivedKey);
validateGet(is, testFile);
}
catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
finally {
if (is != null) {
try {
is.close();
} catch (Throwable t) {}
}
if (client != null) {
try {
client.close();
} catch (Throwable t) {}
}
}
}
/**
* Tests the PUT/GET operations for regular (non-content-addressable) streams.
*/
@Test
public void testRegularStream() {
final JobID jobID = JobID.generate();
final String key = "testkey3";
try {
final File testFile = File.createTempFile("testfile", ".dat");
testFile.deleteOnExit();
prepareTestFile(testFile);
BlobClient client = null;
InputStream is = null;
try {
final InetSocketAddress serverAddress = new InetSocketAddress("localhost", BLOB_SSL_SERVER.getPort());
client = new BlobClient(serverAddress, sslClientConfig);
// Store the data
is = new FileInputStream(testFile);
client.put(jobID, key, is);
is.close();
is = null;
// Retrieve the data
is = client.get(jobID, key);
validateGet(is, testFile);
}
finally {
if (is != null) {
is.close();
}
if (client != null) {
client.close();
}
}
}
catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
/**
* Tests the static {@link BlobClient#uploadJarFiles(InetSocketAddress, Configuration, List)} helper.
*/
private void uploadJarFile(BlobServer blobServer, Configuration blobClientConfig) throws Exception {
final File testFile = File.createTempFile("testfile", ".dat");
testFile.deleteOnExit();
prepareTestFile(testFile);
InetSocketAddress serverAddress = new InetSocketAddress("localhost", blobServer.getPort());
List<BlobKey> blobKeys = BlobClient.uploadJarFiles(serverAddress, blobClientConfig,
Collections.singletonList(new Path(testFile.toURI())));
assertEquals(1, blobKeys.size());
try (BlobClient blobClient = new BlobClient(serverAddress, blobClientConfig)) {
InputStream is = blobClient.get(blobKeys.get(0));
validateGet(is, testFile);
}
}
/**
* Verify ssl client to ssl server upload
*/
@Test
public void testUploadJarFilesHelper() throws Exception {
uploadJarFile(BLOB_SSL_SERVER, sslClientConfig);
}
/**
* Verify ssl client to non-ssl server failure
*/
@Test
public void testSSLClientFailure() throws Exception {
try {
uploadJarFile(BLOB_SERVER, sslClientConfig);
fail("SSL client connected to non-ssl server");
} catch (Exception e) {
// Exception expected
}
}
/**
* Verify non-ssl client to ssl server failure
*/
@Test
public void testSSLServerFailure() throws Exception {
try {
uploadJarFile(BLOB_SSL_SERVER, clientConfig);
fail("Non-SSL client connected to ssl server");
} catch (Exception e) {
// Exception expected
}
}
/**
* Verify non-ssl connection sanity
*/
@Test
public void testNonSSLConnection() throws Exception {
uploadJarFile(BLOB_SERVER, clientConfig);
}
}