2018-04-20 20:58:49 +08:00
|
|
|
#include <algorithm>
|
2021-03-10 02:55:58 +08:00
|
|
|
#include <atomic>
|
2018-04-20 20:58:49 +08:00
|
|
|
#include <chrono>
|
2020-02-24 21:06:21 +08:00
|
|
|
#include <csignal>
|
2018-04-20 20:58:49 +08:00
|
|
|
#include <cstdint>
|
|
|
|
#include <exception>
|
2020-02-24 21:06:21 +08:00
|
|
|
#include <filesystem>
|
2018-04-20 20:58:49 +08:00
|
|
|
#include <functional>
|
|
|
|
#include <limits>
|
2020-02-24 21:06:21 +08:00
|
|
|
#include <map>
|
|
|
|
#include <optional>
|
2020-02-17 21:41:46 +08:00
|
|
|
#include <regex>
|
2020-02-24 21:06:21 +08:00
|
|
|
#include <string>
|
2021-06-14 21:47:57 +08:00
|
|
|
#include <string_view>
|
2018-04-20 20:58:49 +08:00
|
|
|
#include <thread>
|
2016-08-10 16:39:02 +08:00
|
|
|
|
2021-01-21 22:47:56 +08:00
|
|
|
#include <fmt/format.h>
|
2017-07-06 19:53:39 +08:00
|
|
|
#include <gflags/gflags.h>
|
2021-01-21 22:47:56 +08:00
|
|
|
#include <spdlog/common.h>
|
|
|
|
#include <spdlog/sinks/daily_file_sink.h>
|
|
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
2017-05-22 18:31:04 +08:00
|
|
|
|
2020-10-16 18:49:33 +08:00
|
|
|
#include "communication/bolt/v1/constants.hpp"
|
2020-03-03 17:38:50 +08:00
|
|
|
#include "helpers.hpp"
|
2020-02-24 21:06:21 +08:00
|
|
|
#include "py/py.hpp"
|
Extract communication to static library
Summary:
Session specifics have been move out of the Bolt `executing` state, and
are accessed via pure virtual Session type. Our server is templated on
the session and we are setting the concrete type, so there should be no
virtual call overhead. Abstract Session is used to indicate the
interface, this could have also been templated, but the explicit
interface definition makes it clearer.
Specific session implementation for running Memgraph is now implemented
in memgraph_bolt, which instantiates the concrete session type. This may
not be 100% appropriate place, but Memgraph specific session isn't
needed anywhere else.
Bolt/communication tests now use a dummy session and depend only on
communication, which significantly improves test run times.
All these changes make the communication a library which doesn't depend
on storage nor the database. Only shared connection points, which aren't
part of the base communication library are:
* glue/conversion -- which converts between storage and bolt types, and
* communication/result_stream_faker -- templated, but used in tests and query/repl
Depends on D1453
Reviewers: mferencevic, buda, mtomic, msantl
Reviewed By: mferencevic, mtomic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D1456
2018-07-10 22:18:19 +08:00
|
|
|
#include "query/exceptions.hpp"
|
2020-02-24 21:06:21 +08:00
|
|
|
#include "query/interpreter.hpp"
|
2021-03-18 18:03:42 +08:00
|
|
|
#include "query/plan/operator.hpp"
|
2020-01-15 20:58:41 +08:00
|
|
|
#include "query/procedure/module.hpp"
|
2020-02-24 21:06:21 +08:00
|
|
|
#include "query/procedure/py_module.hpp"
|
|
|
|
#include "requests/requests.hpp"
|
2021-06-14 21:47:57 +08:00
|
|
|
#include "storage/v2/isolation_level.hpp"
|
2020-02-05 22:05:18 +08:00
|
|
|
#include "storage/v2/storage.hpp"
|
2020-02-24 21:06:21 +08:00
|
|
|
#include "storage/v2/view.hpp"
|
2018-06-20 19:46:54 +08:00
|
|
|
#include "telemetry/telemetry.hpp"
|
2021-03-10 02:55:58 +08:00
|
|
|
#include "utils/event_counter.hpp"
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
#include "utils/file.hpp"
|
2017-06-09 21:48:40 +08:00
|
|
|
#include "utils/flag_validation.hpp"
|
2021-01-21 22:47:56 +08:00
|
|
|
#include "utils/logging.hpp"
|
2021-02-23 03:51:46 +08:00
|
|
|
#include "utils/memory_tracker.hpp"
|
2021-03-05 17:24:08 +08:00
|
|
|
#include "utils/readable_size.hpp"
|
2020-02-24 21:06:21 +08:00
|
|
|
#include "utils/signals.hpp"
|
2020-01-15 20:58:41 +08:00
|
|
|
#include "utils/string.hpp"
|
2020-02-24 21:06:21 +08:00
|
|
|
#include "utils/sysinfo/memory.hpp"
|
|
|
|
#include "utils/terminate_handler.hpp"
|
|
|
|
#include "version.hpp"
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
|
2020-04-06 19:59:37 +08:00
|
|
|
// Communication libraries must be included after query libraries are included.
|
|
|
|
// This is to enable compilation of the binary when linking with old OpenSSL
|
|
|
|
// libraries (as on CentOS 7).
|
|
|
|
//
|
|
|
|
// The OpenSSL library available on CentOS 7 is v1.0.0, that version includes
|
|
|
|
// `libkrb5` in its public API headers (that we include in our communication
|
|
|
|
// stack). The `libkrb5` library has `#define`s for `TRUE` and `FALSE`. Those
|
|
|
|
// defines clash with Antlr's usage of `TRUE` and `FALSE` as enumeration keys.
|
|
|
|
// Because of that the definitions of `TRUE` and `FALSE` that are inherited
|
|
|
|
// from `libkrb5` must be included after the Antlr includes. Hence,
|
|
|
|
// communication headers must be included after query headers.
|
|
|
|
#include "communication/bolt/v1/exceptions.hpp"
|
|
|
|
#include "communication/bolt/v1/session.hpp"
|
|
|
|
#include "communication/init.hpp"
|
|
|
|
#include "communication/server.hpp"
|
|
|
|
#include "communication/session.hpp"
|
|
|
|
#include "glue/communication.hpp"
|
|
|
|
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2020-02-24 21:06:21 +08:00
|
|
|
#include "audit/log.hpp"
|
|
|
|
#include "auth/auth.hpp"
|
2020-02-05 22:05:18 +08:00
|
|
|
#include "glue/auth.hpp"
|
|
|
|
#endif
|
|
|
|
|
2021-06-14 21:47:57 +08:00
|
|
|
namespace {
|
|
|
|
std::string GetAllowedEnumValuesString(const auto &mappings) {
|
|
|
|
std::vector<std::string> allowed_values;
|
|
|
|
allowed_values.reserve(mappings.size());
|
|
|
|
std::transform(mappings.begin(), mappings.end(), std::back_inserter(allowed_values),
|
|
|
|
[](const auto &mapping) { return std::string(mapping.first); });
|
|
|
|
return utils::Join(allowed_values, ", ");
|
|
|
|
}
|
|
|
|
|
|
|
|
enum class ValidationError : uint8_t { EmptyValue, InvalidValue };
|
|
|
|
|
|
|
|
utils::BasicResult<ValidationError> IsValidEnumValueString(const auto &value, const auto &mappings) {
|
|
|
|
if (value.empty()) {
|
|
|
|
return ValidationError::EmptyValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (std::find_if(mappings.begin(), mappings.end(), [&](const auto &mapping) { return mapping.first == value; }) ==
|
|
|
|
mappings.cend()) {
|
|
|
|
return ValidationError::InvalidValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Enum>
|
|
|
|
std::optional<Enum> StringToEnum(const auto &value, const auto &mappings) {
|
|
|
|
const auto mapping_iter =
|
|
|
|
std::find_if(mappings.begin(), mappings.end(), [&](const auto &mapping) { return mapping.first == value; });
|
|
|
|
if (mapping_iter == mappings.cend()) {
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mapping_iter->second;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2020-02-24 21:06:21 +08:00
|
|
|
// Bolt server flags.
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_string(bolt_address, "0.0.0.0", "IP address on which the Bolt server should listen.");
|
|
|
|
DEFINE_VALIDATED_int32(bolt_port, 7687, "Port on which the Bolt server should listen.",
|
2018-01-15 21:03:07 +08:00
|
|
|
FLAG_IN_RANGE(0, std::numeric_limits<uint16_t>::max()));
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_VALIDATED_int32(bolt_num_workers, std::max(std::thread::hardware_concurrency(), 1U),
|
|
|
|
"Number of workers used by the Bolt server. By default, this will be the "
|
|
|
|
"number of processing units available on the machine.",
|
|
|
|
FLAG_IN_RANGE(1, INT32_MAX));
|
|
|
|
DEFINE_VALIDATED_int32(bolt_session_inactivity_timeout, 1800,
|
|
|
|
"Time in seconds after which inactive Bolt sessions will be "
|
|
|
|
"closed.",
|
|
|
|
FLAG_IN_RANGE(1, INT32_MAX));
|
|
|
|
DEFINE_string(bolt_cert_file, "", "Certificate file which should be used for the Bolt server.");
|
|
|
|
DEFINE_string(bolt_key_file, "", "Key file which should be used for the Bolt server.");
|
2020-02-24 21:06:21 +08:00
|
|
|
DEFINE_string(bolt_server_name_for_init, "",
|
|
|
|
"Server name which the database should send to the client in the "
|
|
|
|
"Bolt INIT message.");
|
2018-08-22 21:00:16 +08:00
|
|
|
|
2019-10-09 22:00:02 +08:00
|
|
|
// General purpose flags.
|
2020-03-03 17:38:50 +08:00
|
|
|
// NOTE: The `data_directory` flag must be the same here and in
|
|
|
|
// `mg_import_csv`. If you change it, make sure to change it there as well.
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_string(data_directory, "mg_data", "Path to directory in which to save all permanent data.");
|
|
|
|
DEFINE_HIDDEN_string(log_link_basename, "", "Basename used for symlink creation to the last log file.");
|
2020-02-24 21:06:21 +08:00
|
|
|
DEFINE_uint64(memory_warning_threshold, 1024,
|
|
|
|
"Memory warning threshold, in MB. If Memgraph detects there is "
|
|
|
|
"less available RAM it will log a warning. Set to 0 to "
|
|
|
|
"disable.");
|
2019-10-09 22:00:02 +08:00
|
|
|
|
|
|
|
// Storage flags.
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_VALIDATED_uint64(storage_gc_cycle_sec, 30, "Storage garbage collector interval (in seconds).",
|
2019-10-09 22:00:02 +08:00
|
|
|
FLAG_IN_RANGE(1, 24 * 3600));
|
2020-03-03 17:38:50 +08:00
|
|
|
// NOTE: The `storage_properties_on_edges` flag must be the same here and in
|
|
|
|
// `mg_import_csv`. If you change it, make sure to change it there as well.
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_bool(storage_properties_on_edges, false, "Controls whether edges have properties.");
|
|
|
|
DEFINE_bool(storage_recover_on_startup, false, "Controls whether the storage recovers persisted data on startup.");
|
2019-10-09 22:00:02 +08:00
|
|
|
DEFINE_VALIDATED_uint64(storage_snapshot_interval_sec, 0,
|
|
|
|
"Storage snapshot creation interval (in seconds). Set "
|
|
|
|
"to 0 to disable periodic snapshot creation.",
|
|
|
|
FLAG_IN_RANGE(0, 7 * 24 * 3600));
|
|
|
|
DEFINE_bool(storage_wal_enabled, false,
|
|
|
|
"Controls whether the storage uses write-ahead-logging. To enable "
|
|
|
|
"WAL periodic snapshots must be enabled.");
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_VALIDATED_uint64(storage_snapshot_retention_count, 3, "The number of snapshots that should always be kept.",
|
2019-10-09 22:00:02 +08:00
|
|
|
FLAG_IN_RANGE(1, 1000000));
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_VALIDATED_uint64(storage_wal_file_size_kib, storage::Config::Durability().wal_file_size_kibibytes,
|
|
|
|
"Minimum file size of each WAL file.", FLAG_IN_RANGE(1, 1000 * 1024));
|
|
|
|
DEFINE_VALIDATED_uint64(storage_wal_file_flush_every_n_tx, storage::Config::Durability().wal_file_flush_every_n_tx,
|
|
|
|
"Issue a 'fsync' call after this amount of transactions are written to the "
|
|
|
|
"WAL file. Set to 1 for fully synchronous operation.",
|
|
|
|
FLAG_IN_RANGE(1, 1000000));
|
|
|
|
DEFINE_bool(storage_snapshot_on_exit, false, "Controls whether the storage creates another snapshot on exit.");
|
2019-10-09 22:00:02 +08:00
|
|
|
|
2018-06-20 19:46:54 +08:00
|
|
|
DEFINE_bool(telemetry_enabled, false,
|
|
|
|
"Set to true to enable telemetry. We collect information about the "
|
|
|
|
"running system (CPU and memory information) and information about "
|
|
|
|
"the database runtime (vertex and edge counts and resource usage) "
|
|
|
|
"to allow for easier improvement of the product.");
|
Extract communication to static library
Summary:
Session specifics have been move out of the Bolt `executing` state, and
are accessed via pure virtual Session type. Our server is templated on
the session and we are setting the concrete type, so there should be no
virtual call overhead. Abstract Session is used to indicate the
interface, this could have also been templated, but the explicit
interface definition makes it clearer.
Specific session implementation for running Memgraph is now implemented
in memgraph_bolt, which instantiates the concrete session type. This may
not be 100% appropriate place, but Memgraph specific session isn't
needed anywhere else.
Bolt/communication tests now use a dummy session and depend only on
communication, which significantly improves test run times.
All these changes make the communication a library which doesn't depend
on storage nor the database. Only shared connection points, which aren't
part of the base communication library are:
* glue/conversion -- which converts between storage and bolt types, and
* communication/result_stream_faker -- templated, but used in tests and query/repl
Depends on D1453
Reviewers: mferencevic, buda, mtomic, msantl
Reviewed By: mferencevic, mtomic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D1456
2018-07-10 22:18:19 +08:00
|
|
|
|
2019-02-19 20:50:46 +08:00
|
|
|
// Audit logging flags.
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2019-02-19 20:50:46 +08:00
|
|
|
DEFINE_bool(audit_enabled, false, "Set to true to enable audit logging.");
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_VALIDATED_int32(audit_buffer_size, audit::kBufferSizeDefault, "Maximum number of items in the audit log buffer.",
|
2019-02-19 20:50:46 +08:00
|
|
|
FLAG_IN_RANGE(1, INT32_MAX));
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_VALIDATED_int32(audit_buffer_flush_interval_ms, audit::kBufferFlushIntervalMillisDefault,
|
|
|
|
"Interval (in milliseconds) used for flushing the audit log buffer.",
|
|
|
|
FLAG_IN_RANGE(10, INT32_MAX));
|
2020-02-05 22:05:18 +08:00
|
|
|
#endif
|
2019-02-19 20:50:46 +08:00
|
|
|
|
2019-11-25 22:08:16 +08:00
|
|
|
// Query flags.
|
|
|
|
DEFINE_uint64(query_execution_timeout_sec, 180,
|
|
|
|
"Maximum allowed query execution time. Queries exceeding this "
|
|
|
|
"limit will be aborted. Value of 0 means no limit.");
|
|
|
|
|
2021-06-14 21:47:57 +08:00
|
|
|
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
DEFINE_uint64(
|
|
|
|
memory_limit, 0,
|
|
|
|
"Total memory limit in MiB. Set to 0 to use the default values which are 100\% of the phyisical memory if the swap "
|
|
|
|
"is enabled and 90\% of the physical memory otherwise.");
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
using namespace std::literals;
|
|
|
|
constexpr std::array isolation_level_mappings{
|
|
|
|
std::pair{"SNAPSHOT_ISOLATION"sv, storage::IsolationLevel::SNAPSHOT_ISOLATION},
|
|
|
|
std::pair{"READ_COMMITTED"sv, storage::IsolationLevel::READ_COMMITTED},
|
|
|
|
std::pair{"READ_UNCOMMITTED"sv, storage::IsolationLevel::READ_UNCOMMITTED}};
|
|
|
|
|
|
|
|
const std::string isolation_level_help_string =
|
|
|
|
fmt::format("Default isolation level used for the transactions. Allowed values: {}",
|
|
|
|
GetAllowedEnumValuesString(isolation_level_mappings));
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
DEFINE_VALIDATED_string(isolation_level, "SNAPSHOT_ISOLATION", isolation_level_help_string.c_str(), {
|
|
|
|
if (const auto result = IsValidEnumValueString(value, isolation_level_mappings); result.HasError()) {
|
|
|
|
const auto error = result.GetError();
|
|
|
|
switch (error) {
|
|
|
|
case ValidationError::EmptyValue: {
|
|
|
|
std::cout << "Isolation level cannot be empty." << std::endl;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ValidationError::InvalidValue: {
|
|
|
|
std::cout << "Invalid value for isolation level. Allowed values: "
|
|
|
|
<< GetAllowedEnumValuesString(isolation_level_mappings) << std::endl;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
storage::IsolationLevel ParseIsolationLevel() {
|
|
|
|
const auto isolation_level = StringToEnum<storage::IsolationLevel>(FLAGS_isolation_level, isolation_level_mappings);
|
|
|
|
MG_ASSERT(isolation_level, "Invalid isolation level");
|
|
|
|
return *isolation_level;
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t GetMemoryLimit() {
|
|
|
|
if (FLAGS_memory_limit == 0) {
|
|
|
|
auto maybe_total_memory = utils::sysinfo::TotalMemory();
|
|
|
|
MG_ASSERT(maybe_total_memory, "Failed to fetch the total physical memory");
|
|
|
|
const auto maybe_swap_memory = utils::sysinfo::SwapTotalMemory();
|
|
|
|
MG_ASSERT(maybe_swap_memory, "Failed to fetch the total swap memory");
|
|
|
|
|
|
|
|
if (*maybe_swap_memory == 0) {
|
|
|
|
// take only 90% of the total memory
|
|
|
|
*maybe_total_memory *= 9;
|
|
|
|
*maybe_total_memory /= 10;
|
|
|
|
}
|
|
|
|
return *maybe_total_memory * 1024;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We parse the memory as MiB every time
|
|
|
|
return FLAGS_memory_limit * 1024 * 1024;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2021-03-10 19:18:09 +08:00
|
|
|
namespace {
|
|
|
|
std::vector<std::filesystem::path> query_modules_directories;
|
|
|
|
} // namespace
|
|
|
|
DEFINE_VALIDATED_string(query_modules_directory, "",
|
|
|
|
"Directory where modules with custom query procedures are stored. "
|
|
|
|
"NOTE: Multiple comma-separated directories can be defined.",
|
2021-02-18 22:32:43 +08:00
|
|
|
{
|
2021-03-10 19:18:09 +08:00
|
|
|
query_modules_directories.clear();
|
2021-02-18 22:32:43 +08:00
|
|
|
if (value.empty()) return true;
|
2021-03-10 19:18:09 +08:00
|
|
|
const auto directories = utils::Split(value, ",");
|
|
|
|
for (const auto &dir : directories) {
|
|
|
|
if (!utils::DirExists(dir)) {
|
|
|
|
std::cout << "Expected --" << flagname << " to point to directories." << std::endl;
|
|
|
|
std::cout << dir << " is not a directory." << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
query_modules_directories.reserve(directories.size());
|
|
|
|
std::transform(directories.begin(), directories.end(),
|
|
|
|
std::back_inserter(query_modules_directories),
|
|
|
|
[](const auto &dir) { return dir; });
|
|
|
|
return true;
|
2021-02-18 22:32:43 +08:00
|
|
|
});
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
|
2021-01-21 22:47:56 +08:00
|
|
|
// Logging flags
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_bool(also_log_to_stderr, false, "Log messages go to stderr in addition to logfiles");
|
2021-01-21 22:47:56 +08:00
|
|
|
DEFINE_string(log_file, "", "Path to where the log should be stored.");
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
constexpr std::array log_level_mappings{
|
2021-06-14 21:47:57 +08:00
|
|
|
std::pair{"TRACE"sv, spdlog::level::trace}, std::pair{"DEBUG"sv, spdlog::level::debug},
|
|
|
|
std::pair{"INFO"sv, spdlog::level::info}, std::pair{"WARNING"sv, spdlog::level::warn},
|
|
|
|
std::pair{"ERROR"sv, spdlog::level::err}, std::pair{"CRITICAL"sv, spdlog::level::critical}};
|
2021-01-21 22:47:56 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
const std::string log_level_help_string =
|
2021-06-14 21:47:57 +08:00
|
|
|
fmt::format("Minimum log level. Allowed values: {}", GetAllowedEnumValuesString(log_level_mappings));
|
2021-01-21 22:47:56 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
DEFINE_VALIDATED_string(log_level, "WARNING", log_level_help_string.c_str(), {
|
2021-06-14 21:47:57 +08:00
|
|
|
if (const auto result = IsValidEnumValueString(value, log_level_mappings); result.HasError()) {
|
|
|
|
const auto error = result.GetError();
|
|
|
|
switch (error) {
|
|
|
|
case ValidationError::EmptyValue: {
|
|
|
|
std::cout << "Log level cannot be empty." << std::endl;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ValidationError::InvalidValue: {
|
|
|
|
std::cout << "Invalid value for log level. Allowed values: " << GetAllowedEnumValuesString(log_level_mappings)
|
|
|
|
<< std::endl;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-01-21 22:47:56 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void ParseLogLevel() {
|
2021-06-14 21:47:57 +08:00
|
|
|
const auto log_level = StringToEnum<spdlog::level::level_enum>(FLAGS_log_level, log_level_mappings);
|
|
|
|
MG_ASSERT(log_level, "Invalid log level");
|
|
|
|
spdlog::set_level(*log_level);
|
2021-01-21 22:47:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 5 weeks * 7 days
|
|
|
|
constexpr auto log_retention_count = 35;
|
|
|
|
|
|
|
|
void ConfigureLogging() {
|
|
|
|
std::vector<spdlog::sink_ptr> loggers;
|
|
|
|
|
|
|
|
if (FLAGS_also_log_to_stderr) {
|
2021-02-18 22:32:43 +08:00
|
|
|
loggers.emplace_back(std::make_shared<spdlog::sinks::stderr_color_sink_mt>());
|
2021-01-21 22:47:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!FLAGS_log_file.empty()) {
|
|
|
|
// get local time
|
|
|
|
time_t current_time;
|
|
|
|
struct tm *local_time{nullptr};
|
|
|
|
|
|
|
|
time(¤t_time);
|
|
|
|
local_time = localtime(¤t_time);
|
|
|
|
|
|
|
|
loggers.emplace_back(std::make_shared<spdlog::sinks::daily_file_sink_mt>(
|
2021-02-18 22:32:43 +08:00
|
|
|
FLAGS_log_file, local_time->tm_hour, local_time->tm_min, false, log_retention_count));
|
2021-01-21 22:47:56 +08:00
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
spdlog::set_default_logger(std::make_shared<spdlog::logger>("memgraph_log", loggers.begin(), loggers.end()));
|
2021-01-21 22:47:56 +08:00
|
|
|
|
|
|
|
spdlog::flush_on(spdlog::level::trace);
|
|
|
|
ParseLogLevel();
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2020-02-24 21:06:21 +08:00
|
|
|
/// Encapsulates Dbms and Interpreter that are passed through the network server
|
|
|
|
/// and worker to the session.
|
|
|
|
#ifdef MG_ENTERPRISE
|
|
|
|
struct SessionData {
|
|
|
|
// Explicit constructor here to ensure that pointers to all objects are
|
|
|
|
// supplied.
|
2021-02-18 22:32:43 +08:00
|
|
|
SessionData(storage::Storage *db, query::InterpreterContext *interpreter_context, auth::Auth *auth,
|
2020-02-24 21:06:21 +08:00
|
|
|
audit::Log *audit_log)
|
2021-02-18 22:32:43 +08:00
|
|
|
: db(db), interpreter_context(interpreter_context), auth(auth), audit_log(audit_log) {}
|
2020-02-24 21:06:21 +08:00
|
|
|
storage::Storage *db;
|
|
|
|
query::InterpreterContext *interpreter_context;
|
|
|
|
auth::Auth *auth;
|
|
|
|
audit::Log *audit_log;
|
|
|
|
};
|
|
|
|
#else
|
|
|
|
struct SessionData {
|
|
|
|
// Explicit constructor here to ensure that pointers to all objects are
|
|
|
|
// supplied.
|
2021-02-18 22:32:43 +08:00
|
|
|
SessionData(storage::Storage *db, query::InterpreterContext *interpreter_context)
|
2020-02-24 21:06:21 +08:00
|
|
|
: db(db), interpreter_context(interpreter_context) {}
|
|
|
|
storage::Storage *db;
|
|
|
|
query::InterpreterContext *interpreter_context;
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
class BoltSession final : public communication::bolt::Session<communication::InputStream, communication::OutputStream> {
|
2020-02-24 21:06:21 +08:00
|
|
|
public:
|
2021-02-18 22:32:43 +08:00
|
|
|
BoltSession(SessionData *data, const io::network::Endpoint &endpoint, communication::InputStream *input_stream,
|
2020-02-24 21:06:21 +08:00
|
|
|
communication::OutputStream *output_stream)
|
2021-02-18 22:32:43 +08:00
|
|
|
: communication::bolt::Session<communication::InputStream, communication::OutputStream>(input_stream,
|
|
|
|
output_stream),
|
2020-02-24 21:06:21 +08:00
|
|
|
db_(data->db),
|
|
|
|
interpreter_(data->interpreter_context),
|
|
|
|
#ifdef MG_ENTERPRISE
|
|
|
|
auth_(data->auth),
|
|
|
|
audit_log_(data->audit_log),
|
|
|
|
#endif
|
|
|
|
endpoint_(endpoint) {
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
using communication::bolt::Session<communication::InputStream, communication::OutputStream>::TEncoder;
|
2020-02-24 21:06:21 +08:00
|
|
|
|
2020-10-16 18:49:33 +08:00
|
|
|
void BeginTransaction() override { interpreter_.BeginTransaction(); }
|
|
|
|
|
|
|
|
void CommitTransaction() override { interpreter_.CommitTransaction(); }
|
|
|
|
|
|
|
|
void RollbackTransaction() override { interpreter_.RollbackTransaction(); }
|
|
|
|
|
|
|
|
std::pair<std::vector<std::string>, std::optional<int>> Interpret(
|
2021-02-18 22:32:43 +08:00
|
|
|
const std::string &query, const std::map<std::string, communication::bolt::Value> ¶ms) override {
|
2020-02-24 21:06:21 +08:00
|
|
|
std::map<std::string, storage::PropertyValue> params_pv;
|
2021-02-18 22:32:43 +08:00
|
|
|
for (const auto &kv : params) params_pv.emplace(kv.first, glue::ToPropertyValue(kv.second));
|
2020-02-24 21:06:21 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2021-02-18 22:32:43 +08:00
|
|
|
audit_log_->Record(endpoint_.address, user_ ? user_->username() : "", query, storage::PropertyValue(params_pv));
|
2020-02-24 21:06:21 +08:00
|
|
|
#endif
|
|
|
|
try {
|
|
|
|
auto result = interpreter_.Prepare(query, params_pv);
|
|
|
|
#ifdef MG_ENTERPRISE
|
|
|
|
if (user_) {
|
|
|
|
const auto &permissions = user_->GetPermissions();
|
2020-10-16 18:49:33 +08:00
|
|
|
for (const auto &privilege : result.privileges) {
|
2021-02-18 22:32:43 +08:00
|
|
|
if (permissions.Has(glue::PrivilegeToPermission(privilege)) != auth::PermissionLevel::GRANT) {
|
2020-02-24 21:06:21 +08:00
|
|
|
interpreter_.Abort();
|
|
|
|
throw communication::bolt::ClientError(
|
|
|
|
"You are not authorized to execute this query! Please contact "
|
|
|
|
"your database administrator.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2020-10-16 18:49:33 +08:00
|
|
|
return {result.headers, result.qid};
|
2020-02-24 21:06:21 +08:00
|
|
|
|
|
|
|
} catch (const query::QueryException &e) {
|
|
|
|
// Wrap QueryException into ClientError, because we want to allow the
|
|
|
|
// client to fix their query.
|
|
|
|
throw communication::bolt::ClientError(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::map<std::string, communication::bolt::Value> Pull(TEncoder *encoder, std::optional<int> n,
|
|
|
|
std::optional<int> qid) override {
|
2020-10-16 18:49:33 +08:00
|
|
|
TypedValueResultStream stream(encoder, db_);
|
|
|
|
return PullResults(stream, n, qid);
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::map<std::string, communication::bolt::Value> Discard(std::optional<int> n, std::optional<int> qid) override {
|
2020-10-16 18:49:33 +08:00
|
|
|
DiscardValueResultStream stream;
|
|
|
|
return PullResults(stream, n, qid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Abort() override { interpreter_.Abort(); }
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
bool Authenticate(const std::string &username, const std::string &password) override {
|
2020-10-16 18:49:33 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
|
|
|
if (!auth_->HasUsers()) return true;
|
|
|
|
user_ = auth_->Authenticate(username, password);
|
|
|
|
return !!user_;
|
|
|
|
#else
|
|
|
|
return true;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<std::string> GetServerNameForInit() override {
|
|
|
|
if (FLAGS_bolt_server_name_for_init.empty()) return std::nullopt;
|
|
|
|
return FLAGS_bolt_server_name_for_init;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
template <typename TStream>
|
2021-02-18 22:32:43 +08:00
|
|
|
std::map<std::string, communication::bolt::Value> PullResults(TStream &stream, std::optional<int> n,
|
|
|
|
std::optional<int> qid) {
|
2020-02-24 21:06:21 +08:00
|
|
|
try {
|
2020-10-16 18:49:33 +08:00
|
|
|
const auto &summary = interpreter_.Pull(&stream, n, qid);
|
2020-02-24 21:06:21 +08:00
|
|
|
std::map<std::string, communication::bolt::Value> decoded_summary;
|
|
|
|
for (const auto &kv : summary) {
|
2021-02-18 22:32:43 +08:00
|
|
|
auto maybe_value = glue::ToBoltValue(kv.second, *db_, storage::View::NEW);
|
2020-02-24 21:06:21 +08:00
|
|
|
if (maybe_value.HasError()) {
|
|
|
|
switch (maybe_value.GetError()) {
|
|
|
|
case storage::Error::DELETED_OBJECT:
|
|
|
|
case storage::Error::SERIALIZATION_ERROR:
|
|
|
|
case storage::Error::VERTEX_HAS_EDGES:
|
|
|
|
case storage::Error::PROPERTIES_DISABLED:
|
|
|
|
case storage::Error::NONEXISTENT_OBJECT:
|
2021-02-18 22:32:43 +08:00
|
|
|
throw communication::bolt::ClientError("Unexpected storage error when streaming summary.");
|
2020-02-24 21:06:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
decoded_summary.emplace(kv.first, std::move(*maybe_value));
|
|
|
|
}
|
|
|
|
return decoded_summary;
|
|
|
|
} catch (const query::QueryException &e) {
|
|
|
|
// Wrap QueryException into ClientError, because we want to allow the
|
|
|
|
// client to fix their query.
|
|
|
|
throw communication::bolt::ClientError(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Wrapper around TEncoder which converts TypedValue to Value
|
|
|
|
/// before forwarding the calls to original TEncoder.
|
|
|
|
class TypedValueResultStream {
|
|
|
|
public:
|
2021-02-18 22:32:43 +08:00
|
|
|
TypedValueResultStream(TEncoder *encoder, const storage::Storage *db) : encoder_(encoder), db_(db) {}
|
2020-02-24 21:06:21 +08:00
|
|
|
|
|
|
|
void Result(const std::vector<query::TypedValue> &values) {
|
|
|
|
std::vector<communication::bolt::Value> decoded_values;
|
|
|
|
decoded_values.reserve(values.size());
|
|
|
|
for (const auto &v : values) {
|
|
|
|
auto maybe_value = glue::ToBoltValue(v, *db_, storage::View::NEW);
|
|
|
|
if (maybe_value.HasError()) {
|
|
|
|
switch (maybe_value.GetError()) {
|
|
|
|
case storage::Error::DELETED_OBJECT:
|
2021-02-18 22:32:43 +08:00
|
|
|
throw communication::bolt::ClientError("Returning a deleted object as a result.");
|
2020-02-24 21:06:21 +08:00
|
|
|
case storage::Error::NONEXISTENT_OBJECT:
|
2021-02-18 22:32:43 +08:00
|
|
|
throw communication::bolt::ClientError("Returning a nonexistent object as a result.");
|
2020-02-24 21:06:21 +08:00
|
|
|
case storage::Error::VERTEX_HAS_EDGES:
|
|
|
|
case storage::Error::SERIALIZATION_ERROR:
|
|
|
|
case storage::Error::PROPERTIES_DISABLED:
|
2021-02-18 22:32:43 +08:00
|
|
|
throw communication::bolt::ClientError("Unexpected storage error when streaming results.");
|
2020-02-24 21:06:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
decoded_values.emplace_back(std::move(*maybe_value));
|
|
|
|
}
|
|
|
|
encoder_->MessageRecord(decoded_values);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
TEncoder *encoder_;
|
|
|
|
// NOTE: Needed only for ToBoltValue conversions
|
|
|
|
const storage::Storage *db_;
|
|
|
|
};
|
|
|
|
|
2020-10-16 18:49:33 +08:00
|
|
|
struct DiscardValueResultStream {
|
|
|
|
void Result(const std::vector<query::TypedValue> &) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-02-24 21:06:21 +08:00
|
|
|
// NOTE: Needed only for ToBoltValue conversions
|
|
|
|
const storage::Storage *db_;
|
|
|
|
query::Interpreter interpreter_;
|
|
|
|
#ifdef MG_ENTERPRISE
|
|
|
|
auth::Auth *auth_;
|
|
|
|
std::optional<auth::User> user_;
|
|
|
|
audit::Log *audit_log_;
|
|
|
|
#endif
|
|
|
|
io::network::Endpoint endpoint_;
|
|
|
|
};
|
|
|
|
|
Extract communication to static library
Summary:
Session specifics have been move out of the Bolt `executing` state, and
are accessed via pure virtual Session type. Our server is templated on
the session and we are setting the concrete type, so there should be no
virtual call overhead. Abstract Session is used to indicate the
interface, this could have also been templated, but the explicit
interface definition makes it clearer.
Specific session implementation for running Memgraph is now implemented
in memgraph_bolt, which instantiates the concrete session type. This may
not be 100% appropriate place, but Memgraph specific session isn't
needed anywhere else.
Bolt/communication tests now use a dummy session and depend only on
communication, which significantly improves test run times.
All these changes make the communication a library which doesn't depend
on storage nor the database. Only shared connection points, which aren't
part of the base communication library are:
* glue/conversion -- which converts between storage and bolt types, and
* communication/result_stream_faker -- templated, but used in tests and query/repl
Depends on D1453
Reviewers: mferencevic, buda, mtomic, msantl
Reviewed By: mferencevic, mtomic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D1456
2018-07-10 22:18:19 +08:00
|
|
|
using ServerT = communication::Server<BoltSession, SessionData>;
|
|
|
|
using communication::ServerContext;
|
|
|
|
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2021-02-18 22:32:43 +08:00
|
|
|
DEFINE_string(auth_user_or_role_name_regex, "[a-zA-Z0-9_.+-@]+",
|
|
|
|
"Set to the regular expression that each user or role name must fulfill.");
|
2020-02-17 21:41:46 +08:00
|
|
|
|
2020-01-15 20:58:41 +08:00
|
|
|
class AuthQueryHandler final : public query::AuthQueryHandler {
|
|
|
|
auth::Auth *auth_;
|
2020-02-17 21:41:46 +08:00
|
|
|
std::regex name_regex_;
|
2020-01-15 20:58:41 +08:00
|
|
|
|
|
|
|
public:
|
2021-02-18 22:32:43 +08:00
|
|
|
AuthQueryHandler(auth::Auth *auth, const std::regex &name_regex) : auth_(auth), name_regex_(name_regex) {}
|
2020-01-15 20:58:41 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
bool CreateUser(const std::string &username, const std::optional<std::string> &password) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(username, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid user name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
return !!auth_->AddUser(username, password);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DropUser(const std::string &username) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(username, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid user name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) return false;
|
|
|
|
return auth_->RemoveUser(username);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void SetPassword(const std::string &username, const std::optional<std::string> &password) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(username, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid user name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) {
|
2021-02-18 22:32:43 +08:00
|
|
|
throw query::QueryRuntimeException("User '{}' doesn't exist.", username);
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
user->UpdatePassword(password);
|
|
|
|
auth_->SaveUser(*user);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CreateRole(const std::string &rolename) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(rolename, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid role name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
return !!auth_->AddRole(rolename);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DropRole(const std::string &rolename) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(rolename, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid role name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto role = auth_->GetRole(rolename);
|
|
|
|
if (!role) return false;
|
|
|
|
return auth_->RemoveRole(rolename);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<query::TypedValue> GetUsernames() override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
std::vector<query::TypedValue> usernames;
|
|
|
|
const auto &users = auth_->AllUsers();
|
|
|
|
usernames.reserve(users.size());
|
|
|
|
for (const auto &user : users) {
|
|
|
|
usernames.emplace_back(user.username());
|
|
|
|
}
|
|
|
|
return usernames;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<query::TypedValue> GetRolenames() override {
|
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
std::vector<query::TypedValue> rolenames;
|
|
|
|
const auto &roles = auth_->AllRoles();
|
|
|
|
rolenames.reserve(roles.size());
|
|
|
|
for (const auto &role : roles) {
|
|
|
|
rolenames.emplace_back(role.rolename());
|
|
|
|
}
|
|
|
|
return rolenames;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::optional<std::string> GetRolenameForUser(const std::string &username) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(username, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid user name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) {
|
2021-02-18 22:32:43 +08:00
|
|
|
throw query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
if (user->role()) return user->role()->rolename();
|
|
|
|
return std::nullopt;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::vector<query::TypedValue> GetUsernamesForRole(const std::string &rolename) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(rolename, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid role name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto role = auth_->GetRole(rolename);
|
|
|
|
if (!role) {
|
2021-02-18 22:32:43 +08:00
|
|
|
throw query::QueryRuntimeException("Role '{}' doesn't exist.", rolename);
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
std::vector<query::TypedValue> usernames;
|
|
|
|
const auto &users = auth_->AllUsersForRole(rolename);
|
|
|
|
usernames.reserve(users.size());
|
|
|
|
for (const auto &user : users) {
|
|
|
|
usernames.emplace_back(user.username());
|
|
|
|
}
|
|
|
|
return usernames;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void SetRole(const std::string &username, const std::string &rolename) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(username, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid user name.");
|
|
|
|
}
|
|
|
|
if (!std::regex_match(rolename, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid role name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) {
|
2021-02-18 22:32:43 +08:00
|
|
|
throw query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
auto role = auth_->GetRole(rolename);
|
|
|
|
if (!role) {
|
2021-02-18 22:32:43 +08:00
|
|
|
throw query::QueryRuntimeException("Role '{}' doesn't exist .", rolename);
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
if (user->role()) {
|
2021-02-18 22:32:43 +08:00
|
|
|
throw query::QueryRuntimeException("User '{}' is already a member of role '{}'.", username,
|
|
|
|
user->role()->rolename());
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
user->SetRole(*role);
|
|
|
|
auth_->SaveUser(*user);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClearRole(const std::string &username) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(username, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid user name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
auto user = auth_->GetUser(username);
|
|
|
|
if (!user) {
|
2021-02-18 22:32:43 +08:00
|
|
|
throw query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
user->ClearRole();
|
|
|
|
auth_->SaveUser(*user);
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::vector<std::vector<query::TypedValue>> GetPrivileges(const std::string &user_or_role) override {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(user_or_role, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid user or role name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
std::vector<std::vector<query::TypedValue>> grants;
|
|
|
|
auto user = auth_->GetUser(user_or_role);
|
|
|
|
auto role = auth_->GetRole(user_or_role);
|
|
|
|
if (!user && !role) {
|
2021-02-18 22:32:43 +08:00
|
|
|
throw query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
if (user) {
|
|
|
|
const auto &permissions = user->GetPermissions();
|
|
|
|
for (const auto &privilege : query::kPrivilegesAll) {
|
|
|
|
auto permission = glue::PrivilegeToPermission(privilege);
|
|
|
|
auto effective = permissions.Has(permission);
|
|
|
|
if (permissions.Has(permission) != auth::PermissionLevel::NEUTRAL) {
|
|
|
|
std::vector<std::string> description;
|
|
|
|
auto user_level = user->permissions().Has(permission);
|
|
|
|
if (user_level == auth::PermissionLevel::GRANT) {
|
|
|
|
description.emplace_back("GRANTED TO USER");
|
|
|
|
} else if (user_level == auth::PermissionLevel::DENY) {
|
|
|
|
description.emplace_back("DENIED TO USER");
|
|
|
|
}
|
|
|
|
if (user->role()) {
|
|
|
|
auto role_level = user->role()->permissions().Has(permission);
|
|
|
|
if (role_level == auth::PermissionLevel::GRANT) {
|
|
|
|
description.emplace_back("GRANTED TO ROLE");
|
|
|
|
} else if (role_level == auth::PermissionLevel::DENY) {
|
|
|
|
description.emplace_back("DENIED TO ROLE");
|
|
|
|
}
|
|
|
|
}
|
2021-02-18 22:32:43 +08:00
|
|
|
grants.push_back({query::TypedValue(auth::PermissionToString(permission)),
|
|
|
|
query::TypedValue(auth::PermissionLevelToString(effective)),
|
|
|
|
query::TypedValue(utils::Join(description, ", "))});
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const auto &permissions = role->permissions();
|
|
|
|
for (const auto &privilege : query::kPrivilegesAll) {
|
|
|
|
auto permission = glue::PrivilegeToPermission(privilege);
|
|
|
|
auto effective = permissions.Has(permission);
|
|
|
|
if (effective != auth::PermissionLevel::NEUTRAL) {
|
|
|
|
std::string description;
|
|
|
|
if (effective == auth::PermissionLevel::GRANT) {
|
|
|
|
description = "GRANTED TO ROLE";
|
|
|
|
} else if (effective == auth::PermissionLevel::DENY) {
|
|
|
|
description = "DENIED TO ROLE";
|
|
|
|
}
|
2021-02-18 22:32:43 +08:00
|
|
|
grants.push_back({query::TypedValue(auth::PermissionToString(permission)),
|
|
|
|
query::TypedValue(auth::PermissionLevelToString(effective)),
|
|
|
|
query::TypedValue(description)});
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return grants;
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void GrantPrivilege(const std::string &user_or_role,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &privileges) override {
|
|
|
|
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
|
|
|
|
// TODO (mferencevic): should we first check that the
|
|
|
|
// privilege is granted/denied/revoked before
|
|
|
|
// unconditionally granting/denying/revoking it?
|
|
|
|
permissions->Grant(permission);
|
|
|
|
});
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void DenyPrivilege(const std::string &user_or_role,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &privileges) override {
|
|
|
|
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
|
|
|
|
// TODO (mferencevic): should we first check that the
|
|
|
|
// privilege is granted/denied/revoked before
|
|
|
|
// unconditionally granting/denying/revoking it?
|
|
|
|
permissions->Deny(permission);
|
|
|
|
});
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void RevokePrivilege(const std::string &user_or_role,
|
|
|
|
const std::vector<query::AuthQuery::Privilege> &privileges) override {
|
|
|
|
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
|
|
|
|
// TODO (mferencevic): should we first check that the
|
|
|
|
// privilege is granted/denied/revoked before
|
|
|
|
// unconditionally granting/denying/revoking it?
|
|
|
|
permissions->Revoke(permission);
|
|
|
|
});
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
template <class TEditFun>
|
2021-02-18 22:32:43 +08:00
|
|
|
void EditPermissions(const std::string &user_or_role, const std::vector<query::AuthQuery::Privilege> &privileges,
|
|
|
|
const TEditFun &edit_fun) {
|
2020-02-17 21:41:46 +08:00
|
|
|
if (!std::regex_match(user_or_role, name_regex_)) {
|
|
|
|
throw query::QueryRuntimeException("Invalid user or role name.");
|
|
|
|
}
|
2020-01-15 20:58:41 +08:00
|
|
|
try {
|
|
|
|
std::lock_guard<std::mutex> lock(auth_->WithLock());
|
|
|
|
std::vector<auth::Permission> permissions;
|
|
|
|
permissions.reserve(privileges.size());
|
|
|
|
for (const auto &privilege : privileges) {
|
|
|
|
permissions.push_back(glue::PrivilegeToPermission(privilege));
|
|
|
|
}
|
|
|
|
auto user = auth_->GetUser(user_or_role);
|
|
|
|
auto role = auth_->GetRole(user_or_role);
|
|
|
|
if (!user && !role) {
|
2021-02-18 22:32:43 +08:00
|
|
|
throw query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
|
2020-01-15 20:58:41 +08:00
|
|
|
}
|
|
|
|
if (user) {
|
|
|
|
for (const auto &permission : permissions) {
|
|
|
|
edit_fun(&user->permissions(), permission);
|
|
|
|
}
|
|
|
|
auth_->SaveUser(*user);
|
|
|
|
} else {
|
|
|
|
for (const auto &permission : permissions) {
|
|
|
|
edit_fun(&role->permissions(), permission);
|
|
|
|
}
|
|
|
|
auth_->SaveRole(*role);
|
|
|
|
}
|
|
|
|
} catch (const auth::AuthException &e) {
|
|
|
|
throw query::QueryRuntimeException(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-02-05 22:05:18 +08:00
|
|
|
#else
|
|
|
|
class NoAuthInCommunity : public query::QueryRuntimeException {
|
|
|
|
public:
|
|
|
|
NoAuthInCommunity()
|
2021-02-18 22:32:43 +08:00
|
|
|
: query::QueryRuntimeException::QueryRuntimeException("Auth is not supported in Memgraph Community!") {}
|
2020-02-05 22:05:18 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
class AuthQueryHandler final : public query::AuthQueryHandler {
|
|
|
|
public:
|
2021-02-18 22:32:43 +08:00
|
|
|
bool CreateUser(const std::string &, const std::optional<std::string> &) override { throw NoAuthInCommunity(); }
|
2020-02-05 22:05:18 +08:00
|
|
|
|
|
|
|
bool DropUser(const std::string &) override { throw NoAuthInCommunity(); }
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void SetPassword(const std::string &, const std::optional<std::string> &) override { throw NoAuthInCommunity(); }
|
2020-02-05 22:05:18 +08:00
|
|
|
|
|
|
|
bool CreateRole(const std::string &) override { throw NoAuthInCommunity(); }
|
|
|
|
|
|
|
|
bool DropRole(const std::string &) override { throw NoAuthInCommunity(); }
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::vector<query::TypedValue> GetUsernames() override { throw NoAuthInCommunity(); }
|
2020-02-05 22:05:18 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::vector<query::TypedValue> GetRolenames() override { throw NoAuthInCommunity(); }
|
2020-02-05 22:05:18 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::optional<std::string> GetRolenameForUser(const std::string &) override { throw NoAuthInCommunity(); }
|
2020-02-05 22:05:18 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::vector<query::TypedValue> GetUsernamesForRole(const std::string &) override { throw NoAuthInCommunity(); }
|
2020-02-05 22:05:18 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void SetRole(const std::string &, const std::string &) override { throw NoAuthInCommunity(); }
|
2020-02-05 22:05:18 +08:00
|
|
|
|
|
|
|
void ClearRole(const std::string &) override { throw NoAuthInCommunity(); }
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::vector<std::vector<query::TypedValue>> GetPrivileges(const std::string &) override { throw NoAuthInCommunity(); }
|
2020-02-05 22:05:18 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void GrantPrivilege(const std::string &, const std::vector<query::AuthQuery::Privilege> &) override {
|
2020-02-05 22:05:18 +08:00
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void DenyPrivilege(const std::string &, const std::vector<query::AuthQuery::Privilege> &) override {
|
2020-02-05 22:05:18 +08:00
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
void RevokePrivilege(const std::string &, const std::vector<query::AuthQuery::Privilege> &) override {
|
2020-02-05 22:05:18 +08:00
|
|
|
throw NoAuthInCommunity();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
#endif
|
2020-01-15 20:58:41 +08:00
|
|
|
|
2020-02-24 21:06:21 +08:00
|
|
|
// Needed to correctly handle memgraph destruction from a signal handler.
|
|
|
|
// Without having some sort of a flag, it is possible that a signal is handled
|
|
|
|
// when we are exiting main, inside destructors of database::GraphDb and
|
|
|
|
// similar. The signal handler may then initiate another shutdown on memgraph
|
|
|
|
// which is in half destructed state, causing invalid memory access and crash.
|
|
|
|
volatile sig_atomic_t is_shutting_down = 0;
|
|
|
|
|
|
|
|
void InitSignalHandlers(const std::function<void()> &shutdown_fun) {
|
|
|
|
// Prevent handling shutdown inside a shutdown. For example, SIGINT handler
|
|
|
|
// being interrupted by SIGTERM before is_shutting_down is set, thus causing
|
|
|
|
// double shutdown.
|
|
|
|
sigset_t block_shutdown_signals;
|
|
|
|
sigemptyset(&block_shutdown_signals);
|
|
|
|
sigaddset(&block_shutdown_signals, SIGTERM);
|
|
|
|
sigaddset(&block_shutdown_signals, SIGINT);
|
|
|
|
|
|
|
|
// Wrap the shutdown function in a safe way to prevent recursive shutdown.
|
|
|
|
auto shutdown = [shutdown_fun]() {
|
|
|
|
if (is_shutting_down) return;
|
|
|
|
is_shutting_down = 1;
|
|
|
|
shutdown_fun();
|
|
|
|
};
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
MG_ASSERT(utils::SignalHandler::RegisterHandler(utils::Signal::Terminate, shutdown, block_shutdown_signals),
|
2021-01-21 22:47:56 +08:00
|
|
|
"Unable to register SIGTERM handler!");
|
2021-02-18 22:32:43 +08:00
|
|
|
MG_ASSERT(utils::SignalHandler::RegisterHandler(utils::Signal::Interupt, shutdown, block_shutdown_signals),
|
2021-01-21 22:47:56 +08:00
|
|
|
"Unable to register SIGINT handler!");
|
2020-02-24 21:06:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
google::SetUsageMessage("Memgraph database server");
|
|
|
|
gflags::SetVersionString(version_string);
|
|
|
|
|
|
|
|
// Load config before parsing arguments, so that flags from the command line
|
|
|
|
// overwrite the config.
|
2020-03-03 17:38:50 +08:00
|
|
|
LoadConfig("memgraph");
|
2020-02-24 21:06:21 +08:00
|
|
|
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
|
|
|
|
2021-01-21 22:47:56 +08:00
|
|
|
ConfigureLogging();
|
2020-02-24 21:06:21 +08:00
|
|
|
|
|
|
|
// Unhandled exception handler init.
|
|
|
|
std::set_terminate(&utils::TerminateHandler);
|
|
|
|
|
|
|
|
// Initialize Python
|
|
|
|
auto *program_name = Py_DecodeLocale(argv[0], nullptr);
|
2021-01-21 22:47:56 +08:00
|
|
|
MG_ASSERT(program_name);
|
2020-02-24 21:06:21 +08:00
|
|
|
// Set program name, so Python can find its way to runtime libraries relative
|
|
|
|
// to executable.
|
|
|
|
Py_SetProgramName(program_name);
|
|
|
|
PyImport_AppendInittab("_mgp", &query::procedure::PyInitMgpModule);
|
|
|
|
Py_InitializeEx(0 /* = initsigs */);
|
|
|
|
PyEval_InitThreads();
|
|
|
|
Py_BEGIN_ALLOW_THREADS;
|
|
|
|
|
2020-03-09 18:06:01 +08:00
|
|
|
// Add our Python modules to sys.path
|
|
|
|
try {
|
|
|
|
auto exe_path = utils::GetExecutablePath();
|
|
|
|
auto py_support_dir = exe_path.parent_path() / "python_support";
|
|
|
|
if (std::filesystem::is_directory(py_support_dir)) {
|
|
|
|
auto gil = py::EnsureGIL();
|
|
|
|
auto maybe_exc = py::AppendToSysPath(py_support_dir.c_str());
|
|
|
|
if (maybe_exc) {
|
2021-02-18 22:32:43 +08:00
|
|
|
spdlog::error("Unable to load support for embedded Python: {}", *maybe_exc);
|
2020-03-09 18:06:01 +08:00
|
|
|
}
|
|
|
|
} else {
|
2021-02-18 22:32:43 +08:00
|
|
|
spdlog::error("Unable to load support for embedded Python: missing directory {}", py_support_dir);
|
2020-03-09 18:06:01 +08:00
|
|
|
}
|
|
|
|
} catch (const std::filesystem::filesystem_error &e) {
|
2021-01-21 22:47:56 +08:00
|
|
|
spdlog::error("Unable to load support for embedded Python: {}", e.what());
|
2020-03-09 18:06:01 +08:00
|
|
|
}
|
|
|
|
|
2020-02-24 21:06:21 +08:00
|
|
|
// Initialize the communication library.
|
2020-10-20 18:55:13 +08:00
|
|
|
communication::SSLInit sslInit;
|
2020-02-24 21:06:21 +08:00
|
|
|
|
|
|
|
// Initialize the requests library.
|
|
|
|
requests::Init();
|
|
|
|
|
|
|
|
// Start memory warning logger.
|
|
|
|
utils::Scheduler mem_log_scheduler;
|
|
|
|
if (FLAGS_memory_warning_threshold > 0) {
|
2021-03-04 19:20:11 +08:00
|
|
|
auto free_ram = utils::sysinfo::AvailableMemory();
|
2020-02-24 21:06:21 +08:00
|
|
|
if (free_ram) {
|
|
|
|
mem_log_scheduler.Run("Memory warning", std::chrono::seconds(3), [] {
|
2021-03-04 19:20:11 +08:00
|
|
|
auto free_ram = utils::sysinfo::AvailableMemory();
|
2020-02-24 21:06:21 +08:00
|
|
|
if (free_ram && *free_ram / 1024 < FLAGS_memory_warning_threshold)
|
2021-02-18 22:32:43 +08:00
|
|
|
spdlog::warn("Running out of available RAM, only {} MB left", *free_ram / 1024);
|
2020-02-24 21:06:21 +08:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Kernel version for the `MemAvailable` value is from: man procfs
|
2021-01-21 22:47:56 +08:00
|
|
|
spdlog::warn(
|
|
|
|
"You have an older kernel version (<3.14) or the /proc "
|
|
|
|
"filesystem isn't available so remaining memory warnings "
|
|
|
|
"won't be available.");
|
2020-02-24 21:06:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
std::cout << "You are running Memgraph v" << gflags::VersionString() << std::endl;
|
2020-01-17 20:02:50 +08:00
|
|
|
|
2020-02-05 22:05:18 +08:00
|
|
|
auto data_directory = std::filesystem::path(FLAGS_data_directory);
|
|
|
|
|
|
|
|
#ifdef MG_ENTERPRISE
|
2019-02-19 20:50:46 +08:00
|
|
|
// All enterprise features should be constructed before the main database
|
|
|
|
// storage. This will cause them to be destructed *after* the main database
|
|
|
|
// storage. That way any errors that happen during enterprise features
|
|
|
|
// destruction won't have an impact on the storage engine.
|
|
|
|
// Example: When the main storage is destructed it makes a snapshot. When
|
|
|
|
// audit logging is destructed it syncs all pending data to disk and that can
|
|
|
|
// fail. That is why it must be destructed *after* the main database storage
|
|
|
|
// to minimise the impact of their failure on the main storage.
|
|
|
|
|
|
|
|
// Begin enterprise features initialization
|
|
|
|
|
|
|
|
// Auth
|
2019-10-09 22:00:02 +08:00
|
|
|
auth::Auth auth{data_directory / "auth"};
|
2019-02-19 20:50:46 +08:00
|
|
|
|
|
|
|
// Audit log
|
2021-02-18 22:32:43 +08:00
|
|
|
audit::Log audit_log{data_directory / "audit", FLAGS_audit_buffer_size, FLAGS_audit_buffer_flush_interval_ms};
|
2019-02-19 20:50:46 +08:00
|
|
|
// Start the log if enabled.
|
|
|
|
if (FLAGS_audit_enabled) {
|
|
|
|
audit_log.Start();
|
|
|
|
}
|
|
|
|
// Setup SIGUSR2 to be used for reopening audit log files, when e.g. logrotate
|
|
|
|
// rotates our audit logs.
|
2021-02-18 22:32:43 +08:00
|
|
|
MG_ASSERT(utils::SignalHandler::RegisterHandler(utils::Signal::User2, [&audit_log]() { audit_log.ReopenLog(); }),
|
|
|
|
"Unable to register SIGUSR2 handler!");
|
2019-02-19 20:50:46 +08:00
|
|
|
|
|
|
|
// End enterprise features initialization
|
2020-02-05 22:05:18 +08:00
|
|
|
#endif
|
2019-02-19 20:50:46 +08:00
|
|
|
|
2021-03-05 17:24:08 +08:00
|
|
|
const auto memory_limit = GetMemoryLimit();
|
|
|
|
spdlog::info("Memory limit set to {}", utils::GetReadableSize(memory_limit));
|
|
|
|
utils::total_memory_tracker.SetHardLimit(memory_limit);
|
2019-02-19 20:50:46 +08:00
|
|
|
|
2021-03-04 19:20:11 +08:00
|
|
|
// Main storage and execution engines initialization
|
2019-10-09 22:00:02 +08:00
|
|
|
storage::Config db_config{
|
2021-02-18 22:32:43 +08:00
|
|
|
.gc = {.type = storage::Config::Gc::Type::PERIODIC, .interval = std::chrono::seconds(FLAGS_storage_gc_cycle_sec)},
|
2019-10-09 22:00:02 +08:00
|
|
|
.items = {.properties_on_edges = FLAGS_storage_properties_on_edges},
|
2021-02-18 22:32:43 +08:00
|
|
|
.durability = {.storage_directory = FLAGS_data_directory,
|
|
|
|
.recover_on_startup = FLAGS_storage_recover_on_startup,
|
|
|
|
.snapshot_retention_count = FLAGS_storage_snapshot_retention_count,
|
|
|
|
.wal_file_size_kibibytes = FLAGS_storage_wal_file_size_kib,
|
|
|
|
.wal_file_flush_every_n_tx = FLAGS_storage_wal_file_flush_every_n_tx,
|
2021-06-14 21:47:57 +08:00
|
|
|
.snapshot_on_exit = FLAGS_storage_snapshot_on_exit},
|
|
|
|
.transaction = {.isolation_level = ParseIsolationLevel()}};
|
2019-10-09 22:00:02 +08:00
|
|
|
if (FLAGS_storage_snapshot_interval_sec == 0) {
|
2021-01-21 22:47:56 +08:00
|
|
|
if (FLAGS_storage_wal_enabled) {
|
|
|
|
LOG_FATAL(
|
|
|
|
"In order to use write-ahead-logging you must enable "
|
|
|
|
"periodic snapshots by setting the snapshot interval to a "
|
|
|
|
"value larger than 0!");
|
2021-02-18 22:32:43 +08:00
|
|
|
db_config.durability.snapshot_wal_mode = storage::Config::Durability::SnapshotWalMode::DISABLED;
|
2021-01-21 22:47:56 +08:00
|
|
|
}
|
2019-10-09 22:00:02 +08:00
|
|
|
} else {
|
|
|
|
if (FLAGS_storage_wal_enabled) {
|
2021-02-18 22:32:43 +08:00
|
|
|
db_config.durability.snapshot_wal_mode = storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL;
|
2019-10-09 22:00:02 +08:00
|
|
|
} else {
|
2021-02-18 22:32:43 +08:00
|
|
|
db_config.durability.snapshot_wal_mode = storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT;
|
2019-10-09 22:00:02 +08:00
|
|
|
}
|
2021-02-18 22:32:43 +08:00
|
|
|
db_config.durability.snapshot_interval = std::chrono::seconds(FLAGS_storage_snapshot_interval_sec);
|
2019-10-09 22:00:02 +08:00
|
|
|
}
|
|
|
|
storage::Storage db(db_config);
|
2021-05-14 21:38:59 +08:00
|
|
|
query::InterpreterContext interpreter_context{&db, FLAGS_data_directory};
|
2020-04-23 00:07:17 +08:00
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
query::SetExecutionTimeout(&interpreter_context, FLAGS_query_execution_timeout_sec);
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2019-10-07 23:31:25 +08:00
|
|
|
SessionData session_data{&db, &interpreter_context, &auth, &audit_log};
|
2020-02-05 22:05:18 +08:00
|
|
|
#else
|
|
|
|
SessionData session_data{&db, &interpreter_context};
|
|
|
|
#endif
|
2018-06-20 23:44:47 +08:00
|
|
|
|
2021-03-10 19:18:09 +08:00
|
|
|
query::procedure::gModuleRegistry.SetModulesDirectory(query_modules_directories);
|
|
|
|
query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories();
|
2020-02-24 21:06:21 +08:00
|
|
|
|
2020-02-05 22:05:18 +08:00
|
|
|
#ifdef MG_ENTERPRISE
|
2021-02-18 22:32:43 +08:00
|
|
|
AuthQueryHandler auth_handler(&auth, std::regex(FLAGS_auth_user_or_role_name_regex));
|
2020-02-05 22:05:18 +08:00
|
|
|
#else
|
|
|
|
AuthQueryHandler auth_handler;
|
|
|
|
#endif
|
2020-01-15 20:58:41 +08:00
|
|
|
interpreter_context.auth = &auth_handler;
|
2018-07-06 15:28:05 +08:00
|
|
|
|
2018-06-20 23:44:47 +08:00
|
|
|
ServerContext context;
|
|
|
|
std::string service_name = "Bolt";
|
2019-12-08 18:42:59 +08:00
|
|
|
if (!FLAGS_bolt_key_file.empty() && !FLAGS_bolt_cert_file.empty()) {
|
|
|
|
context = ServerContext(FLAGS_bolt_key_file, FLAGS_bolt_cert_file);
|
2018-06-20 23:44:47 +08:00
|
|
|
service_name = "BoltS";
|
2021-01-21 22:47:56 +08:00
|
|
|
spdlog::info("Using secure Bolt connection (with SSL)");
|
2020-10-20 21:49:10 +08:00
|
|
|
} else {
|
2021-01-21 22:47:56 +08:00
|
|
|
spdlog::warn("Using non-secure Bolt connection (without SSL)");
|
2018-06-20 23:44:47 +08:00
|
|
|
}
|
|
|
|
|
2021-02-18 22:32:43 +08:00
|
|
|
ServerT server({FLAGS_bolt_address, static_cast<uint16_t>(FLAGS_bolt_port)}, &session_data, &context,
|
|
|
|
FLAGS_bolt_session_inactivity_timeout, service_name, FLAGS_bolt_num_workers);
|
2017-12-19 19:40:30 +08:00
|
|
|
|
2018-06-20 19:46:54 +08:00
|
|
|
// Setup telemetry
|
2019-04-23 17:00:49 +08:00
|
|
|
std::optional<telemetry::Telemetry> telemetry;
|
2018-06-20 19:46:54 +08:00
|
|
|
if (FLAGS_telemetry_enabled) {
|
2021-02-18 22:32:43 +08:00
|
|
|
telemetry.emplace("https://telemetry.memgraph.com/88b5e7e8-746a-11e8-9f85-538a9e9690cc/",
|
|
|
|
data_directory / "telemetry", std::chrono::minutes(10));
|
2021-03-10 02:55:58 +08:00
|
|
|
telemetry->AddCollector("storage", [&db]() -> nlohmann::json {
|
2019-12-11 22:45:34 +08:00
|
|
|
auto info = db.GetInfo();
|
|
|
|
return {{"vertices", info.vertex_count}, {"edges", info.edge_count}};
|
|
|
|
});
|
2021-03-10 02:55:58 +08:00
|
|
|
telemetry->AddCollector("event_counters", []() -> nlohmann::json {
|
|
|
|
nlohmann::json ret;
|
|
|
|
for (size_t i = 0; i < EventCounter::End(); ++i) {
|
|
|
|
ret[EventCounter::GetName(i)] = EventCounter::global_counters[i].load(std::memory_order_relaxed);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
});
|
2021-03-18 18:03:42 +08:00
|
|
|
telemetry->AddCollector("query_module_counters",
|
|
|
|
[]() -> nlohmann::json { return query::plan::CallProcedure::GetAndResetCounters(); });
|
2019-12-11 22:45:34 +08:00
|
|
|
}
|
2018-06-20 19:46:54 +08:00
|
|
|
|
2017-12-19 19:40:30 +08:00
|
|
|
// Handler for regular termination signals
|
2019-11-25 22:08:16 +08:00
|
|
|
auto shutdown = [&server, &interpreter_context] {
|
2021-01-21 22:47:56 +08:00
|
|
|
// Server needs to be shutdown first and then the database. This prevents
|
|
|
|
// a race condition when a transaction is accepted during server shutdown.
|
2017-12-19 19:40:30 +08:00
|
|
|
server.Shutdown();
|
2021-01-21 22:47:56 +08:00
|
|
|
// After the server is notified to stop accepting and processing
|
|
|
|
// connections we tell the execution engine to stop processing all pending
|
|
|
|
// queries.
|
2019-11-25 22:08:16 +08:00
|
|
|
query::Shutdown(&interpreter_context);
|
2017-12-19 19:40:30 +08:00
|
|
|
};
|
|
|
|
InitSignalHandlers(shutdown);
|
2018-04-20 20:58:49 +08:00
|
|
|
|
2021-01-21 22:47:56 +08:00
|
|
|
MG_ASSERT(server.Start(), "Couldn't start the Bolt server!");
|
2018-01-10 20:56:12 +08:00
|
|
|
server.AwaitShutdown();
|
Integrate loading openCypher module procedures
Summary:
All mgp_* symbols are exported from Memgraph executable, no other
symbols should be visible.
The primary C API header, mg_procedure.h, is now part of the
installation. Also, added a shippable query module example.
Directory `query_modules` is meant to contain sources of modules we
write and ship as part of the installation. Currently, there's only an
example module, but there may be potentially more. Some modules could
only be installed as part of the enterprise release.
For Memgraph to load custom procedures, it needs to be started with a
flag pointing to a directory with compiled shared libraries implementing
those procedures.
Reviewers: mferencevic, ipaljak, llugovic, dsantl, buda
Reviewed By: mferencevic
Subscribers: pullbot
Differential Revision: https://phabricator.memgraph.io/D2538
2019-10-31 23:36:34 +08:00
|
|
|
query::procedure::gModuleRegistry.UnloadAllModules();
|
2017-12-19 19:40:30 +08:00
|
|
|
|
2020-02-24 21:06:21 +08:00
|
|
|
Py_END_ALLOW_THREADS;
|
|
|
|
// Shutdown Python
|
|
|
|
Py_Finalize();
|
|
|
|
PyMem_RawFree(program_name);
|
2021-02-23 03:51:46 +08:00
|
|
|
|
|
|
|
utils::total_memory_tracker.LogPeakMemoryUsage();
|
2020-02-24 21:06:21 +08:00
|
|
|
return 0;
|
2019-12-09 18:31:27 +08:00
|
|
|
}
|