blob: 7091527e153f1becf81438e51142fbfe3d0d9dab [file] [log] [blame]
/*
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.
*/
#ifndef __TEST_EMBEDDED_ADS_HPP__
#define __TEST_EMBEDDED_ADS_HPP__
#include "exception.hpp"
#include "options.hpp"
#include "test_utils.hpp"
#include "tlog.hpp"
#include "scoped_lock.hpp"
#include <string>
#ifdef _WIN32
#define putenv _putenv
#endif
#include <uv.h>
// TODO: This should be broken out in the future if required by more than one test (currently
// Authentication tests)
// Defines for ADS configuration
#define EMBEDDED_ADS_JAR_FILENAME "embedded-ads.jar"
#define EMBEDDED_ADS_CONFIGURATION_DIRECTORY "ads_config"
#define EMBEDDED_ADS_CONFIGURATION_FILE "krb5.conf"
#define CASSANDRA_KEYTAB_ADS_CONFIGURATION_FILE "cassandra.keytab"
#define DSE_KEYTAB_ADS_CONFIGURATION_FILE "dse.keytab"
#define DSE_USER_KEYTAB_ADS_CONFIGURATION_FILE "dseuser.keytab"
#define UNKNOWN_KEYTAB_ADS_CONFIGURATION_FILE "unknown.keytab"
#define BILL_KEYTAB_ADS_CONFIGURATION_FILE "bill.keytab"
#define BOB_KEYTAB_ADS_CONFIGURATION_FILE "bob.keytab"
#define CHARLIE_KEYTAB_ADS_CONFIGURATION_FILE "charlie.keytab"
#define STEVE_KEYTAB_ADS_CONFIGURATION_FILE "steve.keytab"
#define REALM "DATASTAX.COM"
#define DSE_SERVICE_PRINCIPAL "dse/_HOST@DATASTAX.COM"
#define CASSANDRA_USER "cassandra"
#define CASSANDRA_PASSWORD "cassandra"
#define CASSANDRA_USER_PRINCIPAL "cassandra@DATASTAX.COM"
#define DSE_USER "dseuser"
#define DSE_USER_PRINCIPAL "dseuser@DATASTAX.COM"
#define UNKNOWN "unknown"
#define UNKNOWN_PRINCIPAL "unknown@DATASTAX.COM"
#define BILL_PRINCIPAL "bill@DATASTAX.COM"
#define BOB_PRINCIPAL "bob@DATASTAX.COM"
#define CHARLIE_PRINCIPAL "charlie@DATASTAX.COM"
#define STEVE_PRINCIPAL "steve@DATASTAX.COM"
// Output buffer size for spawn pipe(s)
#define OUTPUT_BUFFER_SIZE 10240
namespace test {
/**
* Embedded ADS for easily authenticating with DSE using Kerberos
*/
class EmbeddedADS {
/**
* Result for command execution
*/
typedef struct CommandResult_ {
/**
* Error code (e.g. exit status)
*/
int error_code;
/**
* Standard output from executing command
*/
std::string standard_output;
/**
* Standard error from executing command
*/
std::string standard_error;
CommandResult_()
: error_code(-1) {}
} CommandResult;
public:
/**
* @throws EmbeddedADS::Exception If applications are not available to operate the ADS
* properly
*/
EmbeddedADS() {
// TODO: Update test to work with remote deployments
#ifdef _WIN32
// Unable to execute ADS locally and use remote DSE cluster
throw Exception("ADS Server will not be Created: Must run locally with DSE cluster");
#endif
#ifdef CASS_USE_LIBSSH2
if (Options::deployment_type() == CCM::DeploymentType::REMOTE) {
throw Exception("ADS Server will not be Created: Must run locally with DSE cluster");
}
#endif
// Initialize the mutex
uv_mutex_init(&mutex_);
// Check to see if all applications and files are available for ADS
bool is_useable = true;
std::string message;
if (!is_java_available()) {
is_useable = false;
message += "Java";
}
if (!is_kerberos_client_available()) {
is_useable = false;
if (!message.empty()) {
message += " and ";
}
message += "Kerberos clients (kinit/kdestroy)";
}
if (!Utils::file_exists(EMBEDDED_ADS_JAR_FILENAME)) {
is_useable = false;
if (!message.empty()) {
message += " and ";
}
message += "embedded ADS JAR file";
}
if (!is_useable) {
message = "Unable to Create ADS Server: Missing " + message;
throw Exception(message);
}
}
~EmbeddedADS() {
terminate_process();
uv_mutex_destroy(&mutex_);
}
/**
* Start the ADS process
*/
void start_process() { uv_thread_create(&thread_, EmbeddedADS::process_start, NULL); }
/**
* Terminate the ADS process
*/
void terminate_process() {
uv_process_kill(&process_, SIGTERM);
uv_thread_join(&thread_);
// Reset the static variables
configuration_directory_ = "";
configuration_file_ = "";
cassandra_keytab_file_ = "";
dse_keytab_file_ = "";
dseuser_keytab_file_ = "";
unknown_keytab_file_ = "";
bill_keytab_file_ = "";
bob_keytab_file_ = "";
charlie_keytab_file_ = "";
steve_keytab_file_ = "";
is_initialized_ = false;
}
/**
* Flag to determine if the ADS process is fully initialized
*
* @return True is ADS is initialized; false otherwise
*/
static bool is_initialized() {
datastax::internal::ScopedMutex lock(&mutex_);
return is_initialized_;
}
/**
* Get the configuration director being used by the ADS process
*
* @return Absolute path to the ADS configuration directory; empty string
* indicates ADS was not started properly
*/
static std::string get_configuration_directory() { return configuration_directory_; }
/**
* Get the configuration file being used by the ADS process
*
* @return Absolute path to the ADS configuration file; empty string indicates
* ADS was not started properly
*/
static std::string get_configuration_file() { return configuration_file_; }
/**
* Get the Cassandra keytab configuration file being used by the ADS process
*
* @return Absolute path to the Cassandra keytab configuration file; empty
* string indicates ADS was not started properly
*/
static std::string get_cassandra_keytab_file() { return cassandra_keytab_file_; }
/**
* Get the DSE keytab configuration file being used by the ADS process
*
* @return Absolute path to the DSE keytab configuration file; empty
* string indicates ADS was not started properly
*/
static std::string get_dse_keytab_file() { return dse_keytab_file_; }
/**
* Get the DSE user keytab configuration file being used by the ADS process
*
* @return Absolute path to the DSE user keytab configuration file; empty
* string indicates ADS was not started properly
*/
static std::string get_dseuser_keytab_file() { return dseuser_keytab_file_; }
/**
* Get the unknown keytab configuration file being used by the ADS process
*
* @return Absolute path to the unknown keytab configuration file; empty
* string indicates ADS was not started properly
*/
static std::string get_unknown_keytab_file() { return unknown_keytab_file_; }
/**
* Get the Bill keytab configuration file being used by the ADS process
*
* @return Absolute path to the Bill keytab configuration file; empty string
* indicates ADS was not started properly
*/
static std::string get_bill_keytab_file() { return bill_keytab_file_; }
/**
* Get the Bob keytab configuration file being used by the ADS process
*
* @return Absolute path to the Bob keytab configuration file; empty string
* indicates ADS was not started properly
*/
static std::string get_bob_keytab_file() { return bob_keytab_file_; }
/**
* Get the Charlie keytab configuration file being used by the ADS process
*
* @return Absolute path to the Charlie keytab configuration file; empty
* string indicates ADS was not started properly
*/
static std::string get_charlie_keytab_file() { return charlie_keytab_file_; }
/**
* Get the Steve keytab configuration file being used by the ADS process
*
* @return Absolute path to the Steve keytab configuration file; empty string
* string indicates ADS was not started properly
*/
static std::string get_steve_keytab_file() { return steve_keytab_file_; }
/**
* Check to see if the Kerberos client binaries are Heimdal
*
* @return True if Kerberos implementation is Heimdal; false otherwise
*/
static bool is_kerberos_client_heimdal() {
if (is_kerberos_client_available()) {
// kinit
char* kinit_args[3];
kinit_args[0] = const_cast<char*>("kinit");
kinit_args[1] = const_cast<char*>("--version");
kinit_args[2] = NULL;
// Check the output of the kinit command for Heimdal
CommandResult result = execute_command(kinit_args);
if (result.error_code == 0) {
// Check both outputs
bool is_in_standard_output = Utils::contains(result.standard_output, "Heimdal");
bool is_in_standard_error = Utils::contains(result.standard_error, "Heimdal");
return (is_in_standard_output || is_in_standard_error);
}
}
return false;
}
/**
* Acquire a ticket into the cache of the ADS for a given principal and keytab
* file
*
* @param principal Principal identity
* @param keytab_file Filename of keytab to use
*/
void acquire_ticket(const std::string& principal, const std::string& keytab_file) {
char* args[6];
args[0] = const_cast<char*>("kinit");
args[1] = const_cast<char*>("-k");
args[2] = const_cast<char*>("-t");
args[3] = const_cast<char*>(keytab_file.c_str());
args[4] = const_cast<char*>(principal.c_str());
args[5] = NULL;
execute_command(args);
}
/**
* Destroy all tickets in the cache
*/
void destroy_tickets() {
char* args[3];
args[0] = const_cast<char*>("kdestroy");
args[1] = const_cast<char*>("-A");
args[2] = NULL;
execute_command(args);
}
/**
* Assign the Kerberos environment for keytab use
*
* @param keytab_file Filename of keytab to use
*/
void use_keytab(const std::string& keytab_file) {
// MIT Kerberos
setenv("KRB5_CLIENT_KTNAME", keytab_file);
// Heimdal
setenv("KRB5_KTNAME", keytab_file);
}
/**
* Clear/Unassign the Kerberos environment for keytab use
*/
void clear_keytab() {
// MIT Kerberos
setenv("KRB5_CLIENT_KTNAME", "");
// Heimdal
setenv("KRB5_KTNAME", "");
}
private:
/**
* Thread for the ADS process to execute in
*/
uv_thread_t thread_;
/**
* Mutex for process piped buffer allocation and reads
*/
static uv_mutex_t mutex_;
/**
* Information regarding spawned process
*/
static uv_process_t process_;
/**
* ADS configuration directory
*/
static std::string configuration_directory_;
/**
* KRB5_CONFIG configuration file
*/
static std::string configuration_file_;
/**
* Cassandra keytab configuration file
*/
static std::string cassandra_keytab_file_;
/**
* DSE keytab configuration file
*/
static std::string dse_keytab_file_;
/**
* DSE user keytab configuration file
*/
static std::string dseuser_keytab_file_;
/**
* Unknown keytab configuration file
*/
static std::string unknown_keytab_file_;
/**
* Bill keytab configuration file
*/
static std::string bill_keytab_file_;
/**
* Bob keytab configuration file
*/
static std::string bob_keytab_file_;
/**
* Charlie keytab configuration file
*/
static std::string charlie_keytab_file_;
/**
* Steve keytab configuration file
*/
static std::string steve_keytab_file_;
/**
* Flag to determine if the ADS process is initialized
*/
static bool is_initialized_;
/**
* Execute a command while supplying the KRB5_CONFIG to the ADS server
* configuration file
*
* @param Process and arguments to execute
* @return Error code returned from executing command
*/
static CommandResult execute_command(char* args[]) {
// Create the loop
uv_loop_t loop;
uv_loop_init(&loop);
uv_process_options_t options;
memset(&options, 0, sizeof(uv_process_options_t));
// Create the options for reading information from the spawn pipes
uv_pipe_t standard_output;
uv_pipe_t error_output;
uv_pipe_init(&loop, &standard_output, 0);
uv_pipe_init(&loop, &error_output, 0);
uv_stdio_container_t stdio[3];
options.stdio_count = 3;
options.stdio = stdio;
options.stdio[0].flags = UV_IGNORE;
options.stdio[1].flags = static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
options.stdio[1].data.stream = (uv_stream_t*)&standard_output;
options.stdio[2].flags = static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
options.stdio[2].data.stream = (uv_stream_t*)&error_output;
// Create the options for the process
options.args = args;
options.exit_cb = EmbeddedADS::process_exit;
options.file = args[0];
// Start the process and process loop (if spawned)
CommandResult result;
uv_process_t process;
result.error_code = uv_spawn(&loop, &process, &options);
if (result.error_code == 0) {
TEST_LOG("Launched " << args[0] << " with ID " << process_.pid);
// Configure the storage for the output pipes
std::string stdout_message;
std::string stderr_message;
standard_output.data = &result.standard_output;
error_output.data = &result.standard_error;
// Start the output thread loops
uv_read_start(reinterpret_cast<uv_stream_t*>(&standard_output),
EmbeddedADS::output_allocation, EmbeddedADS::process_read);
uv_read_start(reinterpret_cast<uv_stream_t*>(&error_output), EmbeddedADS::output_allocation,
EmbeddedADS::process_read);
// Start the process loop
uv_run(&loop, UV_RUN_DEFAULT);
uv_loop_close(&loop);
}
return result;
}
/**
* Check to see if Java is available in order to execute the ADS process
*
* @return True if Java is available; false otherwise
*/
static bool is_java_available() {
char* args[3];
args[0] = const_cast<char*>("java");
args[1] = const_cast<char*>("-help");
args[2] = NULL;
return (execute_command(args).error_code == 0);
}
/**
* Check to see if the Kerberos client binaries are available in order to
* properly execute request for the ADS
*
* @return True if kinit and kdestroy are available; false otherwise
*/
static bool is_kerberos_client_available() {
// kinit
char* kinit_args[3];
kinit_args[0] = const_cast<char*>("kinit");
kinit_args[1] = const_cast<char*>("--help");
kinit_args[2] = NULL;
bool is_kinit_available = (execute_command(kinit_args).error_code == 0);
// kdestroy
char* kdestroy_args[3];
kdestroy_args[0] = const_cast<char*>("kdestroy");
kdestroy_args[1] = const_cast<char*>("--help");
kdestroy_args[2] = NULL;
bool is_kdestroy_available = (execute_command(kdestroy_args).error_code == 0);
return (is_kinit_available && is_kdestroy_available);
}
/**
* uv_thread_create callback for executing the ADS process
*
* @param arg UNUSED
*/
static void process_start(void* arg) {
// Create the configuration directory for the ADS
Utils::mkdir(EMBEDDED_ADS_CONFIGURATION_DIRECTORY);
// Initialize the loop and process arguments
uv_loop_t loop;
uv_loop_init(&loop);
uv_process_options_t options;
memset(&options, 0, sizeof(uv_process_options_t));
char* args[7];
args[0] = const_cast<char*>("java");
args[1] = const_cast<char*>("-jar");
args[2] = const_cast<char*>(EMBEDDED_ADS_JAR_FILENAME);
args[3] = const_cast<char*>("-k");
args[4] = const_cast<char*>("--confdir");
args[5] = const_cast<char*>(EMBEDDED_ADS_CONFIGURATION_DIRECTORY);
args[6] = NULL;
// Create the options for reading information from the spawn pipes
uv_pipe_t standard_output;
uv_pipe_t error_output;
uv_pipe_init(&loop, &standard_output, 0);
uv_pipe_init(&loop, &error_output, 0);
uv_stdio_container_t stdio[3];
options.stdio_count = 3;
options.stdio = stdio;
options.stdio[0].flags = UV_IGNORE;
options.stdio[1].flags = static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
options.stdio[1].data.stream = (uv_stream_t*)&standard_output;
options.stdio[2].flags = static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
options.stdio[2].data.stream = (uv_stream_t*)&error_output;
// Create the options for the process
options.args = args;
options.exit_cb = EmbeddedADS::process_exit;
options.file = args[0];
// Start the process
int error_code = uv_spawn(&loop, &process_, &options);
if (error_code == 0) {
TEST_LOG("Launched " << args[0] << " with ID " << process_.pid);
// Configure the storage for the output pipes
std::string stdout_message;
std::string stderr_message;
standard_output.data = &stdout_message;
error_output.data = &stderr_message;
// Start the output thread loops
uv_read_start(reinterpret_cast<uv_stream_t*>(&standard_output),
EmbeddedADS::output_allocation, EmbeddedADS::process_read);
uv_read_start(reinterpret_cast<uv_stream_t*>(&error_output), EmbeddedADS::output_allocation,
EmbeddedADS::process_read);
// Indicate the ADS configurations
configuration_directory_ = Utils::cwd() + Utils::PATH_SEPARATOR +
EMBEDDED_ADS_CONFIGURATION_DIRECTORY + Utils::PATH_SEPARATOR;
configuration_file_ = configuration_directory_ + EMBEDDED_ADS_CONFIGURATION_FILE;
cassandra_keytab_file_ = configuration_directory_ + CASSANDRA_KEYTAB_ADS_CONFIGURATION_FILE;
dse_keytab_file_ = configuration_directory_ + DSE_KEYTAB_ADS_CONFIGURATION_FILE;
dseuser_keytab_file_ = configuration_directory_ + DSE_USER_KEYTAB_ADS_CONFIGURATION_FILE;
unknown_keytab_file_ = configuration_directory_ + UNKNOWN_KEYTAB_ADS_CONFIGURATION_FILE;
bill_keytab_file_ = configuration_directory_ + BILL_KEYTAB_ADS_CONFIGURATION_FILE;
bob_keytab_file_ = configuration_directory_ + BOB_KEYTAB_ADS_CONFIGURATION_FILE;
charlie_keytab_file_ = configuration_directory_ + CHARLIE_KEYTAB_ADS_CONFIGURATION_FILE;
steve_keytab_file_ = configuration_directory_ + STEVE_KEYTAB_ADS_CONFIGURATION_FILE;
// Inject the configuration environment variable
setenv("KRB5_CONFIG", configuration_file_);
// Start the process loop
uv_run(&loop, UV_RUN_DEFAULT);
uv_loop_close(&loop);
} else {
TEST_LOG_ERROR(uv_strerror(error_code));
}
}
/**
* uv_spawn callback for handling the completion of the process
*
* @param process Process
* @param error_code Error/Exit code
* @param term_signal Terminating signal
*/
static void process_exit(uv_process_t* process, int64_t error_code, int term_signal) {
datastax::internal::ScopedMutex lock(&mutex_);
TEST_LOG("Process " << process->pid << " Terminated: " << error_code);
uv_close(reinterpret_cast<uv_handle_t*>(process), NULL);
}
/**
* uv_read_start callback for allocating memory for the buffer in the pipe
*
* @param handle Handle information for the pipe being read
* @param suggested_size Suggested size for the buffer
* @param buffer Buffer to allocate bytes for
*/
static void output_allocation(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buffer) {
datastax::internal::ScopedMutex lock(&mutex_);
buffer->base = new char[OUTPUT_BUFFER_SIZE];
buffer->len = OUTPUT_BUFFER_SIZE;
}
/**
* uv_read_start callback for processing the buffer in the pipe
*
* @param stream Stream to process (stdout/stderr)
* @param buffer_length Length of the buffer
* @param buffer Buffer to process
*/
static void process_read(uv_stream_t* stream, ssize_t buffer_length, const uv_buf_t* buffer) {
datastax::internal::ScopedMutex lock(&mutex_);
// Get the pipe message contents
std::string* message = reinterpret_cast<std::string*>(stream->data);
if (buffer_length > 0) {
// Process the buffer and determine if the ADS is finished initializing
std::string output(buffer->base, buffer_length);
message->append(output);
if (!is_initialized_ &&
message->find("Principal Initialization Complete") != std::string::npos) {
Utils::msleep(10000); // TODO: Not 100% ready; need to add a better check mechanism
is_initialized_ = true;
}
TEST_LOG(Utils::trim(output));
} else if (buffer_length < 0) {
uv_close(reinterpret_cast<uv_handle_t*>(stream), NULL);
}
// Clean up the memory allocated
delete[] buffer->base;
}
static void setenv(const std::string& name, const std::string& value) {
#ifdef _WIN32
putenv(const_cast<char*>(std::string(name + "=" + value).c_str()));
#else
::setenv(name.c_str(), value.c_str(), 1);
#endif
}
};
} // namespace test
#endif // __TEST_EMBEDDED_ADS_HPP__