diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e87df80 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,101 @@ +# +# This file is part of the ACT library +# +# Copyright (c) 2023 Fabian Posch +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. +# + +cmake_minimum_required(VERSION 3.15) +project( + actsim-cluster-agent + VERSION 0.0.2 + DESCRIPTION "A cluster agent for the actsim simulator." +) + +# Set the program version +add_compile_definitions(PROG_VERSION="0.0.1") + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") +add_compile_options(-Wall + -Wextra + -Wconversion-null + -Wmissing-declarations + -Woverlength-strings) +add_compile_options( + -Wpointer-arith + -Wunused-local-typedefs + -Wunused-result + -Wvarargs + -Wvla + -Wwrite-strings + -Wformat-security + -Wundef) +add_compile_options(-O2) + +include_directories(./) + +# Include shared act libraries +# We need this mostly for file elaboration ahead of cluster deployment +#add_library(ActLib SHARED IMPORTED) +#set_target_properties(ActLib PROPERTIES IMPORTED_LOCATION $ENV{ACT_HOME}/lib/libact.a) + +#add_library(ActCommon SHARED IMPORTED) +#set_target_properties(ActCommon PROPERTIES IMPORTED_LOCATION $ENV{ACT_HOME}/lib/libvlsilib.a) + + +# ATTENTION +# The main executable is defined in the CMakeLists.txt in the ./src folder. + + +# Set the output directory of static libraries (we don't need that) +#set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib) + +# Set the output directory of executables +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) + +set(CMAKE_INSTALL_PREFIX $ENV{ACT_HOME} CACHE PATH "installation path" FORCE) + +add_subdirectory(src) + +# Link the needed libraries into it +target_link_libraries( + ${PROJECT_NAME} +# act-deploy-commands + act-deploy-db-lib +# act-deploy-pipeline-modules-lib +# act-deploy-test-gen-modules-lib +# yaml-cpp +) + +# Add the Postgresql library +target_link_libraries( + ${PROJECT_NAME} + -lpqxx -lpq +) + +# specify install targets +install( + TARGETS actsim-cluster-agent + DESTINATION bin +) + +# We don't provide a library +#install( +# TARGETS dflowchp +# DESTINATION lib +#) \ No newline at end of file diff --git a/include/db_lib/db_client.hpp b/include/db_lib/db_client.hpp new file mode 100644 index 0000000..44919e4 --- /dev/null +++ b/include/db_lib/db_client.hpp @@ -0,0 +1,143 @@ + +/************************************************************************* + * + * Copyright (c) 2023 Fabian Posch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + ************************************************************************** + */ + + +#ifndef __DB_CLIENT_H__ +#define __DB_CLIENT_H__ + + +#include +#include +#include +#include "db_types.hpp" +#include "task.hpp" + +namespace db { + +using namespace std; + +// some database constants +#define MAX_CON_RETRIES 5 + +class Connection { + + public: + + Connection( + db_credentials_t credentials, + std::function setup_function + ); + + Connection( + db_credentials_t credentials + ); + + ~Connection(); + + pqxx::connection* get_connection() { return c; }; + void setup() { + setup_function(this->c); + } + + bool connect(); + void disconnect(); + bool ensure_connected(); + int find_job(std::string p_name, std::string *f_name); + JobStatusType get_job_status(std::string job); + + bool prepare_statements(vector> statements); + bool prepare_statement(std::string name, std::string statement); + bool unprepare_statement(std::string name); + + // Send request takes an arbitrary request method to the database and wraps error handling + // around it. It takes a function with an arbitrary set of arguments and the arguments to give + // that function and returns whatever the function returns. + // Careful with typing here! C++ doesn't quite do the strictest typing once you go deep like this! + // The function is defined here as it can be accessed from outside this library and is templated. + // This means it must be accessible to the compiler at all times. Failing this will cause a linker error. + template + bool send_request(std::function *db_function, Args... args) { + + retry_send: + + if (!ensure_connected()) { + cerr << "Could contact database. Broken database connection." << endl; + return false; + } + + try { + + pqxx::work txn(*c); + + try { + // we assume the lambda takes a pointer to a transaction object as first argument + // and whatever else was given to us as the rest + (*db_function)(&txn, args...); + + // commit the task to the database + txn.commit(); + } catch (const exception& e) { + + // if something happened during the transaction, we roll it back + txn.abort(); + + cerr << "Could not complete database operation. Error in execution." << endl; + cerr << e.what() << endl; + + return false; + } + + } catch (const pqxx::broken_connection& e) { + cerr << "Pipe broke during database operation... Retrying..." << endl; + cerr << e.what() << endl; + + // using a goto here since it'll gracefully retry connection + goto retry_send; + } + + return true; + }; + + private: + + db_credentials_t db_credentials; + pqxx::connection *c; + std::function setup_function; + bool connected; +}; + +bool upload_task(shared_ptr task); + +const vector> uplink_statements = { + {"commit_task", "UPDATE tasks SET t_status = DONE, error = $2, sim_log = $3, sim_trace = $4 WHERE id = $1"} +}; + +const vector> downlink_statements = { + {"check_open_tasks", "SELECT id FROM tasks WHERE t_status=OPEN LIMIT 1"}, + // TODO add inner join to grab pipeline depth + {"fetch_task", "SELECT id, job, t_status, t_type, is_reference, reference, max_plf, error, sim_log FROM tasks WHERE id = $1 LIMIT 1"} +}; + +}; + +#endif diff --git a/include/db_lib/db_types.hpp b/include/db_lib/db_types.hpp new file mode 100644 index 0000000..0e648d1 --- /dev/null +++ b/include/db_lib/db_types.hpp @@ -0,0 +1,303 @@ + +/************************************************************************* + * + * Copyright (c) 2023 Fabian Posch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + ************************************************************************** + */ + +#ifndef __DB_TYPES_HPP__ +#define __DB_TYPES_HPP__ + +#include +#include + +namespace db { + +// credentials for database access +struct db_credentials_t { + std::string server; + bool server_override; + int port; + bool port_override; + std::string uname; + bool uname_override; + std::string pwd; + bool pwd_override; + std::string dbase; + bool dbase_override; + int version; +}; + +// create UUID type to store database 128 UUID type +struct uuid_t { + uint64_t uuid_upper, uuid_lower; + + uuid_t& operator=(uint64_t other) { + this->uuid_lower = other; + this->uuid_upper = 0; + return *this; + } +}; + + +// override comparison operator to make our life easier +inline bool operator==(uuid_t const & lhs, uuid_t const & rhs) { + return lhs.uuid_upper == rhs.uuid_upper && lhs.uuid_lower == rhs.uuid_lower; +} +inline bool operator==(uuid_t const & lhs, int const & rhs) { + return rhs >= 0 && lhs.uuid_lower == (uint64_t)rhs; +} +inline bool operator==(int const & lhs, uuid_t const & rhs) { + return lhs >= 0 && (uint64_t)lhs == rhs.uuid_lower; +} +inline bool operator!=(uuid_t const & lhs, uuid_t const & rhs) { + return lhs.uuid_upper != rhs.uuid_upper || lhs.uuid_lower != rhs.uuid_lower; +} +inline bool operator!=(uuid_t const & lhs, int const & rhs) { + return rhs < 0 || lhs.uuid_lower != (uint64_t)rhs; +} +inline bool operator!=(int const & lhs, uuid_t const & rhs) { + return lhs < 0 || (uint64_t)lhs != rhs.uuid_lower; +} + + +// easier conversion to string +inline std::string to_string(uuid_t const &value) { + std::ostringstream uuid_s; + uuid_s << std::hex; + uuid_s << std::setfill('0') << std::setw(16) << value.uuid_upper; + uuid_s << std::setfill('0') << std::setw(16) << value.uuid_lower; + return uuid_s.str(); +} + +// stream operations +inline std::ostream& operator<<(std::ostream& os, uuid_t& value) { + os << to_string(value); + return os; +}; + +// macro for registering enum classes for PQXX + +template +struct is_enum_class { + static constexpr bool value = std::is_enum_v && !std::is_convertible_v; +}; + +template +struct is_unordered_map_string { + static constexpr bool value = false; +}; + +template +struct is_unordered_map_string> { + static constexpr bool value = true; +}; + +template +struct is_map_key_type_same_as_enum { + static constexpr bool value = std::is_same_v && + std::is_same_v; +}; + +}; + + +/** + * @brief Generate type conversion for enum class macros in the libpqxx library + * + * Calling this macro will generate the code necessary for converting enum class types in yaml-cpp internally. + * For this you additionally need a std::unordered_map, mapping string values to your + * custom enum class type. + * + * For more information see https://libpqxx.readthedocs.io/en/7.4.1/a01360.html + * + * @param T The type you want to generate the translation for + * @param lookup_table the unordered map, mapping string values of your type to the internal representation + * + */ +#define DB_REGISTER_ENUM_CLASS(T, lookup_table, name) \ + namespace pqxx { \ + \ + static_assert(db::is_enum_class::value, "DB_REGISTER_ENUM_TYPE: 'T' must be an enum class"); \ + static_assert(db::is_unordered_map_string::value, "DB_REGISTER_ENUM_TYPE: 'lookup_table' must be std::unordered_map"); \ + static_assert(db::is_map_key_type_same_as_enum::value, "DB_REGISTER_ENUM_TYPE: 'lookup_table' content must be of type T"); \ + \ + \ + template<> \ + std::string const type_name{name}; \ + \ + template<> \ + struct nullness : pqxx::no_null {}; \ + \ + template<> \ + struct string_traits { \ + \ + static T from_string(std::string_view text) { \ + \ + /* turn the string_view into a string */ \ + std::string str(text); \ + \ + /* make sure the enum value exists */ \ + if (lookup_table.find(str) == lookup_table.end()) throw pqxx::conversion_error("Could not convert " + str + " to " + name); \ + \ + return lookup_table[str]; \ + }; \ + \ + static zview to_buf(char *begin, char *end, T const &value) { \ + std::string str = ""; \ + \ + for (auto& it : lookup_table) { \ + if (it.second == value) { \ + str = it.first; \ + break; \ + } \ + } \ + \ + /* make sure we actually have enough space for the enum */ \ + if (std::distance(begin, end) < static_cast(str.size() + 1)) { \ + std::ostringstream str_str; \ + str_str << "Buffer for "; \ + str_str << name; \ + str_str << " to small"; \ + throw conversion_overrun(str_str.str()); \ + }\ + \ + const char* c_str = str.c_str(); \ + \ + std::memcpy(begin, c_str, sizeof(char) * str.length()); \ + \ + return zview(begin, str.length()); \ + }; \ + \ + static char *into_buf(char *begin, char *end, T const &value) { \ + std::string str = ""; \ + \ + for (auto& it : lookup_table) { \ + if (it.second == value) { \ + str = it.first; \ + break; \ + } \ + } \ + \ + /* make sure we actually have enough space for the enum */ \ + if (std::distance(begin, end) < static_cast(str.size() + 1)) { \ + std::ostringstream str_str; \ + str_str << "Buffer for "; \ + str_str << name; \ + str_str << " to small"; \ + throw conversion_overrun(str_str.str()); \ + }\ + \ + const char* c_str = str.c_str(); \ + \ + std::memcpy(begin, c_str, sizeof(char) * str.length()); \ + \ + return begin; \ + }; \ + \ + static std::size_t size_buffer(T const &value) noexcept { \ + \ + std::string str; \ + str = ""; \ + \ + for (auto& it : lookup_table) { \ + if (it.second == value) { \ + str = it.first; \ + break; \ + } \ + } \ + \ + return str.size() + 1; \ + }; \ + }; \ + }; + + +/** + * @brief Conversion extension for uuid_t for the pqxx library + * + * For more information see https://libpqxx.readthedocs.io/en/7.4.1/a01360.html + * + */ +namespace pqxx { + +template<> +std::string const type_name{"uuid_t"}; + +template<> +struct nullness : pqxx::no_null {}; + +template<> +struct string_traits { + + static db::uuid_t from_string(std::string_view text) { + + if (text.size() != 36) { + throw pqxx::conversion_error("Invalid binary string size for uuid_t. Expected: 36; Actual: " + std::to_string(text.size())); + } + + // turn the string_view into a string and remove the '-' + std::string form(text); + form.erase(std::remove(form.begin(), form.end(), '-'), form.end()); + + // then parse into numbers + std::istringstream high_stream(form.substr(0, 16)); + std::istringstream low_stream(form.substr(16, 16)); + + uint64_t low, high; + + high_stream >> std::hex >> high; + low_stream >> std::hex >> low; + + return {high, low}; + }; + + static zview to_buf(char *begin, char *end, db::uuid_t const &value) { + std::string str = db::to_string(value); + + // make sure we actually have enough space for the UUID + if (std::distance(begin, end) < static_cast(str.size() + 1)) throw conversion_overrun("Buffer for UUID to small"); + + const char* c_str = str.c_str(); + + std::memcpy(begin, c_str, sizeof(char) * str.length()); + + return zview(begin, str.length()); + }; + + static char *into_buf(char *begin, char *end, db::uuid_t const &value) { + std::string str = db::to_string(value); + + // make sure we actually have enough space for the UUID + if (std::distance(begin, end) < static_cast(str.size() + 1)) throw conversion_overrun("Buffer for UUID to small"); + + const char* c_str = str.c_str(); + + std::memcpy(begin, c_str, sizeof(char) * str.length()); + + return begin; + }; + + constexpr static std::size_t size_buffer([[maybe_unused]] db::uuid_t const &value) noexcept { return sizeof(char) * 33; }; +}; + +}; + + +#endif \ No newline at end of file diff --git a/include/db_lib/task.hpp b/include/db_lib/task.hpp new file mode 100644 index 0000000..d3601c8 --- /dev/null +++ b/include/db_lib/task.hpp @@ -0,0 +1,296 @@ +/************************************************************************* + * + * Copyright (c) 2023 Fabian Posch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + ************************************************************************** + */ + + +#ifndef __TASK_H__ +#define __TASK_H__ + +#include +#include +#include +#include "db_types.hpp" + +/** + * @brief Representation of the current status of a job. + * + */ +enum class JobStatusType {NOT_STARTED, IN_PROGRESS, FINISHED, HALTED, UNKNOWN}; + +// string mapping for job status type enum +static std::unordered_map job_status_type_st { + {JobStatusType::NOT_STARTED, "not_started"}, + {JobStatusType::IN_PROGRESS, "in_progress"}, + {JobStatusType::FINISHED, "finished"}, + {JobStatusType::HALTED, "halted"}, + {JobStatusType::UNKNOWN, "unknown"} +}; + +inline std::string to_string(JobStatusType const &value) { + return job_status_type_st[value]; +}; + +inline std::ostream& operator<<(std::ostream& os, JobStatusType& value) { + os << to_string(value); + return os; +}; + +JobStatusType convert_to_job_status(std::string status_string); + + +/** + * @brief Defines if a simulation task should be executed a single time or + * dependent on a central coverage model. The latter task type can also be + * executed by more than just a single node to hasten progress. + */ +enum class TaskTypeType {UNKNOWN, SINGLE, MULTI_COV}; + +// string mapping for task type enum +static std::unordered_map task_type_type_st { + {TaskTypeType::UNKNOWN, "unknown"}, + {TaskTypeType::SINGLE, "single"}, + {TaskTypeType::MULTI_COV, "multi_cov"} +}; + +inline std::string to_string(TaskTypeType const &value) { + return task_type_type_st[value]; +}; + +inline std::ostream& operator<<(std::ostream& os, TaskTypeType& value) { + os << to_string(value); + return os; +}; + +TaskTypeType convert_to_task_type(std::string type_string); + + +// TODO check if this is complete +/** + * @brief If a task simulation was aborted or completed with an error, + * this type represents the types of error encountered. + */ +enum class TaskErrorType {UNKNOWN, NONE, SYNTAX, VALUE, CODE, METASTABLE, TIMING, DEADLOCK}; + +// string mapping for task error type enum +static std::unordered_map task_error_type_st { + {TaskErrorType::UNKNOWN, "unknown"}, + {TaskErrorType::NONE, "none"}, + {TaskErrorType::SYNTAX, "syntax"}, + {TaskErrorType::VALUE, "value"}, + {TaskErrorType::CODE, "code"}, + {TaskErrorType::METASTABLE, "metastable"}, + {TaskErrorType::TIMING, "timing"}, + {TaskErrorType::DEADLOCK, "deadlock"} +}; + +inline std::string to_string(TaskErrorType const &value) { + return task_error_type_st[value]; +}; + +inline std::ostream& operator<<(std::ostream& os, TaskErrorType& value) { + os << to_string(value); + return os; +}; + +TaskErrorType convert_to_task_error(std::string error_string); + + +/** + * @brief Representation of teh current status of a task. + * + */ +enum class TaskStatusType {UNKNOWN, OPEN, IN_PROGRESS, DONE}; + +// string mapping for task status enum +static std::unordered_map task_status_type_st { + {TaskStatusType::UNKNOWN, "unknown"}, + {TaskStatusType::OPEN, "open"}, + {TaskStatusType::IN_PROGRESS, "in_progress"}, + {TaskStatusType::DONE, "done"} +}; + +inline std::string to_string(TaskStatusType const &value) { + return task_status_type_st[value]; +}; + +inline std::ostream& operator<<(std::ostream& os, TaskStatusType& value) { + os << to_string(value); + return os; +}; + +TaskStatusType convert_to_task_status(std::string status_string); + +/** + * @brief Task class represents one simulation task and its settings. + */ +class Task { + public: + + Task( db::uuid_t task_uuid, + std::string job_id, + TaskStatusType status, + TaskTypeType type, + bool is_reference, + db::uuid_t reference_uuid, + float max_plf, + TaskErrorType error, + std::string sim_log + ); + + Task( db::uuid_t task_uuid, + std::string job_id, + TaskTypeType type, + bool is_reference, + db::uuid_t reference_uuid, + float max_plf + ); + + ~Task(); + + /** + * @brief Get the UUID of this task + * + * @return db::uuid_t Task UUID + */ + db::uuid_t get_uuid() { return task_uuid; }; + + /** + * @brief Get the ID of the job this task belongs to + * + * @return const char* Job ID + */ + std::string get_job_id() { return job_id; }; + + /** + * @brief Get information whether this task is a one shot simulation or contributes + * to a central coverage model. + * + * @return TaskType + */ + TaskTypeType get_task_type() { return task_type; }; + + /** + * @brief Does this simulation task have a reference task to compare to? + * + * @return true It does + * @return false It does not + */ + bool is_reference_run() { return is_reference; }; + + /** + * @brief Get the reference object UUID + * + * @return The UUID of the reference simulation task + */ + db::uuid_t get_reference() { return reference_uuid; } + + /** + * @brief Get the status of this task + * + * @return TaskStatusType The task status + */ + TaskStatusType get_status() { return status; } + + /** + * @brief Set the status of the task + * + * @param new_status The new status this task should be set to + */ + void set_status(TaskStatusType new_status) { this->status = new_status; } + + /** + * @brief Get the value of the maximum allowed pipeline load factor + * + * @return float + */ + float get_max_plf() { return max_plf; }; + + /** + * @brief Get the error with which this simulation task was completed + * + * @return TaskErrorType + */ + TaskErrorType get_error() { return error; }; + + /** + * @brief Set the error with which this task was completed + * + * @param error + */ + void set_error(TaskErrorType error) { this->error = error; }; + + /** + * @brief Get the simulation log of this task. Empty if not yet simulated. + * + * @return std::string The simulation log in JSON format + */ + std::string get_log() { return sim_log; }; + + /** + * @brief Set the simulation log + * + * @param log + */ + void set_log(std::string log) { this->sim_log = log; }; + + /** + * @brief Get the simulation trace of this task. Empty if not yet simulated. + * + * @return std::string The simulation log in TODO format + */ + std::string get_trace() { return trace; }; + + /** + * @brief Set the simulation trace + * + * @param trace + */ + void set_trace(std::string trace) { this->trace = trace; }; + + ///////////////////////////////////// + private: + + // UUID of this task in the database + db::uuid_t task_uuid; + + // ID of the job this task belongs to + std::string job_id; + + // Is this task a single shot or does it depend on central coverage metrics? + TaskTypeType task_type; + + // Does this task have a reference run which we should compare the trace to? + bool is_reference; + TaskStatusType status; + db::uuid_t reference_uuid; + + // maximum pipeline load factor + float max_plf; + // error with which this task was completed + TaskErrorType error; + + // simulation output + std::string sim_log; + std::string trace; + +}; + +#endif \ No newline at end of file diff --git a/include/util.h b/include/util.h new file mode 100644 index 0000000..d52ee0c --- /dev/null +++ b/include/util.h @@ -0,0 +1,58 @@ + +/************************************************************************* + * + * Copyright (c) 2023 Fabian Posch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + ************************************************************************** + */ + + +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#include +#include + +// debugging util print functions +#ifdef DEBUG +#define DEBUG_PRINT(msg) std::cerr << "Info: " << msg << std::endl; +#else +#define DEBUG_PRINT(msg) do {} while (0) +#endif + +#define ENUM_CLASS_OPS(T, lookup_table) \ + \ + inline std::string to_string(T const &value) { \ + std::string str = "unknown"; \ + \ + for (auto& it : lookup_table) { \ + if (it.second == value) { \ + str = it.first; \ + break; \ + } \ + } \ + \ + return str; \ + }; \ + \ + inline std::ostream& operator<<(std::ostream& os, T const &rhs) { \ + os << to_string(rhs); \ + return os; \ + }; + +#endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..591a92e --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,24 @@ + +# Grab necessary libraries from ACT_HOME +#include_directories($ENV{ACT_HOME}/include) +include_directories(../include) +#include_directories(../include/commands) +#include_directories(../include/db_lib) + +add_subdirectory(db_lib) +#add_subdirectory(pipeline_lib) +#add_subdirectory(pipeline_modules) +#add_subdirectory(testcase_generators) +#add_subdirectory(commands) + +file( + GLOB proj_SRC + "*.cpp" +) + + +# Add the main executable +add_executable( + ${PROJECT_NAME} + ${proj_SRC} +) diff --git a/src/db_lib/CMakeLists.txt b/src/db_lib/CMakeLists.txt new file mode 100644 index 0000000..bf71910 --- /dev/null +++ b/src/db_lib/CMakeLists.txt @@ -0,0 +1,14 @@ + +include_directories(../../include) +include_directories(../../include/db_lib) + +file( + GLOB db_lib_SRC + "*.cpp" +) + +add_library( + act-deploy-db-lib + STATIC + ${db_lib_SRC} +) \ No newline at end of file diff --git a/src/db_lib/db_client.cpp b/src/db_lib/db_client.cpp new file mode 100644 index 0000000..c79ca86 --- /dev/null +++ b/src/db_lib/db_client.cpp @@ -0,0 +1,215 @@ + +/************************************************************************* + * + * Copyright (c) 2023 Fabian Posch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + ************************************************************************** + */ + + +#include +#include "util.h" +#include "task.hpp" +#include "db_client.hpp" + +namespace db { + +using namespace std; + +string hostname; + +bool connected = false; + + +Connection::Connection( + db_credentials_t credentials, + std::function setup +) : setup_function(move(setup)) { + this->db_credentials = credentials; +} + +Connection::Connection(db_credentials_t credentials) { + this->db_credentials = credentials; +} + +Connection::~Connection() { + if (c != nullptr && c->is_open()) c->close(); +} + +bool Connection::connect() { + bool connection_established = false; + + for (int i = 0; i < MAX_CON_RETRIES; i++) { + try { + // create the connection object + this->c = new pqxx::connection( + "host=" + db_credentials.server + " " + "port=" + std::to_string(db_credentials.port) + " " + "user=" + db_credentials.uname + " " + "password=" + db_credentials.pwd + " " + "dbname=" + db_credentials.dbase + ); + + // make sure the database actually has the version we need + pqxx::work txn(*(this->c)); + + auto db_info = txn.exec1("SELECT * FROM info;"); + txn.commit(); + + if (db_info["db_version"].as() != this->db_credentials.version) { + this->disconnect(); + cerr << "Error: Unsupported database version! Command expects "; + cerr << this->db_credentials.version; + cerr << ", server provides " << db_info["db_version"].as() << "!" << endl; + return false; + } + + if (this->setup_function != nullptr) { + // execute the initialization function + this->setup_function(c); + } + + connection_established = true; + + } catch (const exception &e) { + cerr << "Error: Could not connect to database:" << endl; + cerr << e.what() << endl; + } + } + + return connection_established; +} + +bool Connection::ensure_connected() { + if (c == nullptr || !c->is_open()) { + c->close(); + return connect(); + } + return true; +} + +void Connection::disconnect() { + if (c != nullptr && c->is_open()) c->close(); +} + +bool Connection::prepare_statements(vector> statements) { + + try { + for (auto statement : statements) { + this->c->prepare(statement.first, statement.second); + } + } catch (const exception &e) { + cerr << "Error: Could not prepare statements!" << endl; + return false; + } + + return true; +} + +bool Connection::prepare_statement(std::string name, std::string statement) { + + try { + this->c->prepare(name, statement); + } catch (const exception &e) { + cerr << "Error: Could not prepare statements!" << endl; + return false; + } + + return true; +} + +bool Connection::unprepare_statement(std::string name) { + + auto unprepare_statement_lambda = [] (pqxx::work* txn, std::string statement) { + txn->exec0("DEALLOCATE " + statement + ";"); + }; + + std::function unprepare_statement_func = unprepare_statement_lambda; + + return this->send_request(&unprepare_statement_func, name); +} + +int Connection::find_job(std::string p_name, std::string *f_name) { + auto check_job_ids_lambda = [](pqxx::work *txn, int *ret, std::string p_name, std::string *f_name) { + pqxx::result res {txn->exec("SELECT id FROM jobs;")}; + + *ret = 0; + + for (auto row : res) { + // check if the ID starts with the partial name we've been given + if (row["id"].as().rfind(p_name, 0) == 0) { + // make sure we haven't found it before + if (*ret == 0) { + *ret = 1; + *f_name = row["id"].as(); + } else { + // guess the partial name is ambiguous + *ret = -1; + // we've already seen two, we don't need more + return; + } + } + } + }; + + std::function check_job_ids_func = check_job_ids_lambda; + + DEBUG_PRINT("Sending request..."); + + int ret; + if (!this->send_request(&check_job_ids_func, &ret, p_name, f_name)) { + std::cerr << "Error: Could not fetch job IDs from database!" << std::endl; + return 1; + } + + DEBUG_PRINT("Request complete."); + + return ret; + +} + +JobStatusType Connection::get_job_status(std::string job) { + auto get_jstatus_lambda = [](pqxx::work *txn, std::string job, JobStatusType *status) { + + try { + pqxx::row res {txn->exec1("SELECT job_status FROM jobs WHERE id='" + job + "';")}; + *status = convert_to_job_status(res["job_status"].as()); + } catch (pqxx::unexpected_rows& e) { + std::cerr << "Error: Fetching job returned nothing or too many rows!" << std::endl; + *status = JobStatusType::UNKNOWN; + } + + }; + + std::function get_jstatus_func = get_jstatus_lambda; + + DEBUG_PRINT("Sending request..."); + + JobStatusType status; + if (!this->send_request(&get_jstatus_func, job, &status)) { + std::cerr << "Error: Could not fetch job status from database!" << std::endl; + return JobStatusType::UNKNOWN; + } + + DEBUG_PRINT("Request complete."); + + return status; + +} + +} diff --git a/src/db_lib/task.cpp b/src/db_lib/task.cpp new file mode 100644 index 0000000..7a0c55b --- /dev/null +++ b/src/db_lib/task.cpp @@ -0,0 +1,155 @@ +/************************************************************************* + * + * Copyright (c) 2023 Fabian Posch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + ************************************************************************** + */ + + +#include +#include "task.hpp" + + +Task::Task (db::uuid_t task_uuid, + std::string job_id, + TaskStatusType status, + TaskTypeType type, + bool is_reference, + db::uuid_t reference_uuid, + float max_plf, + TaskErrorType error, + std::string sim_log + ) +{ + this->task_uuid = task_uuid; + this->job_id = job_id; + this->status = status; + this->task_type = type; + this->is_reference = is_reference; + this->reference_uuid = reference_uuid; + this->max_plf = max_plf; + this->error = error; + this->sim_log = sim_log; +} + +Task::Task (db::uuid_t task_uuid, + std::string job_id, + TaskTypeType type, + bool is_reference, + db::uuid_t reference_uuid, + float max_plf + ) +{ + this->task_uuid = task_uuid; + this->job_id = job_id; + this->task_type = type; + this->is_reference = is_reference; + this->reference_uuid = reference_uuid; + this->max_plf = max_plf; +} + + +/** + * @brief Convert from pqxx response string to JobStatusType + * + * @param type_string Type returned by pqxx in string form. + * @return JobStatusType + */ +JobStatusType convert_to_job_status(std::string status_string) { + JobStatusType status = JobStatusType::UNKNOWN; + + for (auto pair : job_status_type_st) { + if (pair.second == status_string) { + status = pair.first; + break; + } + } + + if (status == JobStatusType::UNKNOWN) { + std::cerr << "Warning: Job status type unknown or could not be parsed. Given status was " << status_string << std::endl; + } + + return status; +} + + +/** + * @brief Convert from pqxx response string to TaskTypeType + * + * @param type_string Type returned by pqxx in string form. + * @return TaskTypeType + */ +TaskTypeType convert_to_task_type(std::string type_string) { + if (type_string == "SINGLE") { + return TaskTypeType::SINGLE; + } else if (type_string == "MULTI_COV") { + return TaskTypeType::MULTI_COV; + } else { + std::cerr << "Error: Could not parse task type \"" << type_string << "\"" << std::endl; + return TaskTypeType::UNKNOWN; + } +} + + +/** + * @brief Convert from pqxx response string to TaskErrorType + * + * @param error_string Error returned by pqxx in string form. + * @return TaskErrorType + */ +TaskErrorType convert_to_task_error(std::string error_string) { + if (error_string == "NONE") { + return TaskErrorType::NONE; + } else if (error_string == "SYNTAX") { + return TaskErrorType::SYNTAX; + } else if (error_string == "VALUE") { + return TaskErrorType::VALUE; + } else if (error_string == "CODE") { + return TaskErrorType::CODE; + } else if (error_string == "METASTABLE") { + return TaskErrorType::METASTABLE; + } else if (error_string == "TIMING") { + return TaskErrorType::TIMING; + } else if (error_string == "DEADLOCK") { + return TaskErrorType::DEADLOCK; + } else { + std::cerr << "Error: Could not parse task error \"" << error_string << "\"" << std::endl; + return TaskErrorType::UNKNOWN; + } +} + + +/** + * @brief Convert from pqxx response string to TaskStatusType + * + * @param status_string Status returned by pqxx in string form. + * @return TaskStatusType + */ +TaskStatusType convert_to_task_status(std::string status_string) { + if (status_string == "OPEN") { + return TaskStatusType::OPEN; + } else if (status_string == "IN_PROGRESS") { + return TaskStatusType::IN_PROGRESS; + } else if (status_string == "DONE") { + return TaskStatusType::DONE; + } else { + std::cerr << "Error: Could not parse task status \"" << status_string << "\"" << std::endl; + return TaskStatusType::UNKNOWN; + } +} +