From 1b7ed4dba1459ae1e4982fdc09c7ec018c375f7c Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 13 Feb 2014 07:17:22 +0000 Subject: [PATCH] clickhouse-server: loading users, profiles and quotas form separate config file. [#METR-8956] --- dbms/include/DB/Interpreters/Context.h | 18 ++++- dbms/include/DB/Interpreters/Quota.h | 55 ++++++++++--- dbms/include/DB/Interpreters/Settings.h | 3 +- dbms/include/DB/Interpreters/Users.h | 16 ++-- dbms/src/Interpreters/Context.cpp | 36 ++++++--- dbms/src/Interpreters/Quota.cpp | 100 +++++++++++++++++------- dbms/src/Interpreters/Settings.cpp | 14 +--- dbms/src/Server/HTTPHandler.cpp | 2 +- dbms/src/Server/Server.cpp | 95 ++++++++++++++++++++-- dbms/src/Server/Server.h | 26 ++++++ 10 files changed, 278 insertions(+), 87 deletions(-) diff --git a/dbms/include/DB/Interpreters/Context.h b/dbms/include/DB/Interpreters/Context.h index f176f6fae06..b2f924b2cb6 100644 --- a/dbms/include/DB/Interpreters/Context.h +++ b/dbms/include/DB/Interpreters/Context.h @@ -50,6 +50,8 @@ typedef std::pair DatabaseAndTableName; typedef std::map > ViewDependencies; typedef std::vector Dependencies; +typedef Poco::AutoPtr ConfigurationPtr; + /** Набор известных объектов, которые могут быть использованы в запросе. * Разделяемая часть. Порядок членов (порядок их уничтожения) очень важен. @@ -89,6 +91,7 @@ struct ContextShared mutable MarkCachePtr mark_cache; /// Кэш засечек в сжатых файлах. ProcessList process_list; /// Исполняющиеся в данный момент запросы. ViewDependencies view_dependencies; /// Текущие зависимости + ConfigurationPtr users_config; /// Конфиг с секциями users, profiles и quotas. /// Кластеры для distributed таблиц /// Создаются при создании Distributed таблиц, так как нужно дождаться пока будут выставлены Settings @@ -143,7 +146,7 @@ private: String user; /// Текущий пользователь. Poco::Net::IPAddress ip_address; /// IP-адрес, с которого задан запрос. - QuotaForIntervals * quota; /// Текущая квота. + QuotaForIntervalsPtr quota; /// Текущая квота. String current_database; /// Текущая БД. String current_query_id; /// Id текущего запроса. NamesAndTypesList columns; /// Столбцы текущей обрабатываемой таблицы. @@ -163,12 +166,18 @@ public: String getPath() const; void setPath(const String & path); - void initUsersFromConfig(); + /** Забрать список пользователей, квот и профилей настроек из этого конфига. + * Список пользователей полностью заменяется. + * Накопленные значения у квоты не сбрасываются, если квота не удалена. + */ + void setUsersConfig(ConfigurationPtr config); + + ConfigurationPtr getUsersConfig(); + void setUser(const String & name, const String & password, const Poco::Net::IPAddress & address, const String & quota_key); String getUser() const { return user; } Poco::Net::IPAddress getIPAddress() const { return ip_address; } - void initQuotasFromConfig(); void setQuota(const String & name, const String & quota_key, const String & user_name, const Poco::Net::IPAddress & address); QuotaForIntervals & getQuota(); @@ -211,7 +220,8 @@ public: /// Установить настройку по имени. void setSetting(const String & name, const Field & value); - + void setSetting(const String & name, const std::string & value); + const TableFunctionFactory & getTableFunctionFactory() const { return shared->table_function_factory; } const FunctionFactory & getFunctionFactory() const { return shared->function_factory; } const AggregateFunctionFactory & getAggregateFunctionFactory() const { return shared->aggregate_function_factory; } diff --git a/dbms/include/DB/Interpreters/Quota.h b/dbms/include/DB/Interpreters/Quota.h index cd090fc10e4..ffaacb7e6e3 100644 --- a/dbms/include/DB/Interpreters/Quota.h +++ b/dbms/include/DB/Interpreters/Quota.h @@ -50,7 +50,19 @@ struct QuotaValues memset(this, 0, sizeof(*this)); } - void initFromConfig(const String & config_elem); + void initFromConfig(const String & config_elem, Poco::Util::AbstractConfiguration & config); + + bool operator== (const QuotaValues & rhs) const + { + return + queries == rhs.queries && + errors == rhs.errors && + result_rows == rhs.result_rows && + result_bytes == rhs.result_bytes && + read_rows == rhs.read_rows && + read_bytes == rhs.read_bytes && + execution_time == rhs.execution_time; + } }; @@ -65,7 +77,7 @@ struct QuotaForInterval QuotaForInterval() : rounded_time() {} QuotaForInterval(time_t duration_) : duration(duration_) {} - void initFromConfig(const String & config_elem, time_t duration_); + void initFromConfig(const String & config_elem, time_t duration_, Poco::Util::AbstractConfiguration & config); /// Увеличить соответствующее значение. void addQuery(time_t current_time, const String & quota_name); @@ -82,6 +94,14 @@ struct QuotaForInterval /// Получить текст, описывающий, какая часть квоты израсходована. String toString() const; + bool operator== (const QuotaForInterval & rhs) const + { + return + rounded_time == rhs.rounded_time && + duration == rhs.duration && + max == rhs.max && + used == rhs.used; + } private: /// Сбросить счётчик использованных ресурсов, если соответствующий интервал, за который считается квота, прошёл. void updateTime(time_t current_time); @@ -99,10 +119,10 @@ private: typedef std::map Container; Container cont; - Quota * parent; + std::string name; public: - QuotaForIntervals(Quota * parent_) : parent(parent_) {} + QuotaForIntervals(const std::string & name_ = "") : name(name_) {} /// Есть ли хотя бы один интервал, за который считается квота? bool empty() const @@ -110,7 +130,11 @@ public: return cont.empty(); } - void initFromConfig(const String & config_elem); + void initFromConfig(const String & config_elem, Poco::Util::AbstractConfiguration & config); + + /// Обновляет максимальные значения значениями из quota. + /// Удаляет интервалы, которых нет в quota, добавляет интревалы, которых нет здесь, но есть в quota. + void setMax(const QuotaForIntervals & quota); void addQuery(time_t current_time); void addError(time_t current_time); @@ -123,29 +147,37 @@ public: /// Получить текст, описывающий, какая часть квоты израсходована. String toString() const; + + bool operator== (const QuotaForIntervals & rhs) const + { + return cont == rhs.cont && name == rhs.name; + } }; +typedef Poco::SharedPtr QuotaForIntervalsPtr; + /// Ключ квоты -> квоты за интервалы. Если квота не допускает ключей, то накопленные значения хранятся по ключу 0. struct Quota { - typedef std::unordered_map Container; + typedef std::unordered_map Container; String name; /// Максимальные значения из конфига. QuotaForIntervals max; /// Максимальные и накопленные значения для разных ключей. + /// Для всех ключей максимальные значения одинаковы и взяты из max. Container quota_for_keys; Poco::FastMutex mutex; bool is_keyed; bool keyed_by_ip; - Quota() : max(this), is_keyed(false), keyed_by_ip(false) {} + Quota() : is_keyed(false), keyed_by_ip(false) {} - void initFromConfig(const String & config_elem, const String & name_); - QuotaForIntervals & get(const String & quota_key, const String & user_name, const Poco::Net::IPAddress & ip); + void loadFromConfig(const String & config_elem, const String & name_, Poco::Util::AbstractConfiguration & config); + QuotaForIntervalsPtr get(const String & quota_key, const String & user_name, const Poco::Net::IPAddress & ip); }; @@ -157,8 +189,9 @@ private: Container cont; public: - void initFromConfig(); - QuotaForIntervals & get(const String & name, const String & quota_key, const String & user_name, const Poco::Net::IPAddress & ip); + void loadFromConfig(Poco::Util::AbstractConfiguration & config); + QuotaForIntervalsPtr get(const String & name, const String & quota_key, + const String & user_name, const Poco::Net::IPAddress & ip); }; } diff --git a/dbms/include/DB/Interpreters/Settings.h b/dbms/include/DB/Interpreters/Settings.h index e8afcd5b1ed..7956c0e735e 100644 --- a/dbms/include/DB/Interpreters/Settings.h +++ b/dbms/include/DB/Interpreters/Settings.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -102,7 +103,7 @@ struct Settings /** Установить настройки из профиля (в конфиге сервера, в одном профиле может быть перечислено много настроек). * Профиль также может быть установлен с помощью функций set, как настройка profile. */ - void setProfile(const String & profile_name); + void setProfile(const String & profile_name, Poco::Util::AbstractConfiguration & config); /// Прочитать настройки из буфера. Они записаны как набор name-value пар, идущих подряд, заканчивающихся пустым name. void deserialize(ReadBuffer & buf); diff --git a/dbms/include/DB/Interpreters/Users.h b/dbms/include/DB/Interpreters/Users.h index 4e0726799cc..4728109e30a 100644 --- a/dbms/include/DB/Interpreters/Users.h +++ b/dbms/include/DB/Interpreters/Users.h @@ -225,10 +225,8 @@ public: return false; } - void addFromConfig(const String & config_elem) + void addFromConfig(const String & config_elem, Poco::Util::AbstractConfiguration & config) { - Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config(); - Poco::Util::AbstractConfiguration::Keys config_keys; config.keys(config_elem, config_keys); @@ -266,16 +264,14 @@ struct User AddressPatterns addresses; - User(const String & name_, const String & config_elem) + User(const String & name_, const String & config_elem, Poco::Util::AbstractConfiguration & config) : name(name_) { - Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config(); - password = config.getString(config_elem + ".password"); profile = config.getString(config_elem + ".profile"); quota = config.getString(config_elem + ".quota"); - addresses.addFromConfig(config_elem + ".networks"); + addresses.addFromConfig(config_elem + ".networks", config); } /// Для вставки в контейнер. @@ -291,15 +287,15 @@ private: Container cont; public: - void initFromConfig() + void loadFromConfig(Poco::Util::AbstractConfiguration & config) { - Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config(); + cont.clear(); Poco::Util::AbstractConfiguration::Keys config_keys; config.keys("users", config_keys); for (Poco::Util::AbstractConfiguration::Keys::const_iterator it = config_keys.begin(); it != config_keys.end(); ++it) - cont[*it] = User(*it, "users." + *it); + cont[*it] = User(*it, "users." + *it, config); } const User & get(const String & name, const String & password, const Poco::Net::IPAddress & address) const diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index 848b68cb20a..64d4d11c92f 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -27,10 +27,18 @@ void Context::setPath(const String & path) } -void Context::initUsersFromConfig() +void Context::setUsersConfig(ConfigurationPtr config) { Poco::ScopedLock lock(shared->mutex); - shared->users.initFromConfig(); + shared->users_config = config; + shared->users.loadFromConfig(*shared->users_config); + shared->quotas.loadFromConfig(*shared->users_config); +} + +ConfigurationPtr Context::getUsersConfig() +{ + Poco::ScopedLock lock(shared->mutex); + return shared->users_config; } @@ -47,17 +55,10 @@ void Context::setUser(const String & name, const String & password, const Poco:: } -void Context::initQuotasFromConfig() -{ - Poco::ScopedLock lock(shared->mutex); - shared->quotas.initFromConfig(); -} - - void Context::setQuota(const String & name, const String & quota_key, const String & user_name, const Poco::Net::IPAddress & address) { Poco::ScopedLock lock(shared->mutex); - quota = &shared->quotas.get(name, quota_key, user_name, address); + quota = shared->quotas.get(name, quota_key, user_name, address); } @@ -343,7 +344,20 @@ void Context::setSettings(const Settings & settings_) void Context::setSetting(const String & name, const Field & value) { Poco::ScopedLock lock(shared->mutex); - settings.set(name, value); + if (name == "profile") + settings.setProfile(value.safeGet(), *shared->users_config); + else + settings.set(name, value); +} + + +void Context::setSetting(const String & name, const std::string & value) +{ + Poco::ScopedLock lock(shared->mutex); + if (name == "profile") + settings.setProfile(value, *shared->users_config); + else + settings.set(name, value); } diff --git a/dbms/src/Interpreters/Quota.cpp b/dbms/src/Interpreters/Quota.cpp index 7c7b9acfe6f..102cd3fbec0 100644 --- a/dbms/src/Interpreters/Quota.cpp +++ b/dbms/src/Interpreters/Quota.cpp @@ -6,14 +6,14 @@ #include #include +#include + namespace DB { -void QuotaValues::initFromConfig(const String & config_elem) +void QuotaValues::initFromConfig(const String & config_elem, Poco::Util::AbstractConfiguration & config) { - Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config(); - queries = parse(config.getString(config_elem + ".queries", "0")); errors = parse(config.getString(config_elem + ".errors", "0")); result_rows = parse(config.getString(config_elem + ".result_rows", "0")); @@ -24,11 +24,11 @@ void QuotaValues::initFromConfig(const String & config_elem) } -void QuotaForInterval::initFromConfig(const String & config_elem, time_t duration_) +void QuotaForInterval::initFromConfig(const String & config_elem, time_t duration_, Poco::Util::AbstractConfiguration & config) { rounded_time = 0; duration = duration_; - max.initFromConfig(config_elem); + max.initFromConfig(config_elem, config); } void QuotaForInterval::checkExceeded(time_t current_time, const String & quota_name) @@ -118,7 +118,7 @@ void QuotaForInterval::check(size_t max_amount, size_t used_amount, time_t curre else message << duration << " seconds"; - message << " has been expired. " + message << " has been exceeded. " << resource_name << ": " << used_amount << ", max: " << max_amount << ". " << "Interval will end at " << mysqlxx::DateTime(rounded_time + duration) << "."; @@ -127,10 +127,8 @@ void QuotaForInterval::check(size_t max_amount, size_t used_amount, time_t curre } -void QuotaForIntervals::initFromConfig(const String & config_elem) +void QuotaForIntervals::initFromConfig(const String & config_elem, Poco::Util::AbstractConfiguration & config) { - Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config(); - Poco::Util::AbstractConfiguration::Keys config_keys; config.keys(config_elem, config_keys); @@ -142,44 +140,63 @@ void QuotaForIntervals::initFromConfig(const String & config_elem) String interval_config_elem = config_elem + "." + *it; time_t duration = config.getInt(interval_config_elem + ".duration"); - cont[duration].initFromConfig(interval_config_elem, duration); + cont[duration].initFromConfig(interval_config_elem, duration, config); + } +} + +void QuotaForIntervals::setMax(const QuotaForIntervals & quota) +{ + for (Container::iterator it = cont.begin(); it != cont.end();) + { + if (quota.cont.count(it->first)) + ++it; + else + cont.erase(it++); + } + + for (auto & x : quota.cont) + { + if (!cont.count(x.first)) + cont[x.first] = x.second; + else + cont[x.first].max = x.second.max; } } void QuotaForIntervals::checkExceeded(time_t current_time) { for (Container::reverse_iterator it = cont.rbegin(); it != cont.rend(); ++it) - it->second.checkExceeded(current_time, parent->name); + it->second.checkExceeded(current_time, name); } void QuotaForIntervals::addQuery(time_t current_time) { for (Container::reverse_iterator it = cont.rbegin(); it != cont.rend(); ++it) - it->second.addQuery(current_time, parent->name); + it->second.addQuery(current_time, name); } void QuotaForIntervals::addError(time_t current_time) { for (Container::reverse_iterator it = cont.rbegin(); it != cont.rend(); ++it) - it->second.addError(current_time, parent->name); + it->second.addError(current_time, name); } void QuotaForIntervals::checkAndAddResultRowsBytes(time_t current_time, size_t rows, size_t bytes) { for (Container::reverse_iterator it = cont.rbegin(); it != cont.rend(); ++it) - it->second.checkAndAddResultRowsBytes(current_time, parent->name, rows, bytes); + it->second.checkAndAddResultRowsBytes(current_time, name, rows, bytes); } void QuotaForIntervals::checkAndAddReadRowsBytes(time_t current_time, size_t rows, size_t bytes) { for (Container::reverse_iterator it = cont.rbegin(); it != cont.rend(); ++it) - it->second.checkAndAddReadRowsBytes(current_time, parent->name, rows, bytes); + it->second.checkAndAddReadRowsBytes(current_time, name, rows, bytes); } void QuotaForIntervals::checkAndAddExecutionTime(time_t current_time, Poco::Timespan amount) { for (Container::reverse_iterator it = cont.rbegin(); it != cont.rend(); ++it) - it->second.checkAndAddExecutionTime(current_time, parent->name, amount); + it->second.checkAndAddExecutionTime(current_time, name, amount); } String QuotaForIntervals::toString() const @@ -193,19 +210,34 @@ String QuotaForIntervals::toString() const } -void Quota::initFromConfig(const String & config_elem, const String & name_) +void Quota::loadFromConfig(const String & config_elem, const String & name_, Poco::Util::AbstractConfiguration & config) { name = name_; - Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config(); + bool new_keyed_by_ip = config.has(config_elem + ".keyed_by_ip"); + bool new_is_keyed = new_keyed_by_ip || config.has(config_elem + ".keyed"); - keyed_by_ip = config.has(config_elem + ".keyed_by_ip"); - is_keyed = keyed_by_ip || config.has(config_elem + ".keyed"); + if (new_is_keyed != is_keyed || new_keyed_by_ip != keyed_by_ip) + { + keyed_by_ip = new_keyed_by_ip; + is_keyed = new_is_keyed; + /// Смысл ключей поменялся. Выбросим накопленные значения. + quota_for_keys.clear(); + } - max.initFromConfig(config_elem); + QuotaForIntervals new_max(name); + new_max.initFromConfig(config_elem, config); + if (!(new_max == max)) + { + max = new_max; + for (auto & quota : quota_for_keys) + { + quota.second->setMax(max); + } + } } -QuotaForIntervals & Quota::get(const String & quota_key, const String & user_name, const Poco::Net::IPAddress & ip) +QuotaForIntervalsPtr Quota::get(const String & quota_key, const String & user_name, const Poco::Net::IPAddress & ip) { if (!quota_key.empty() && (!is_keyed || keyed_by_ip)) throw Exception("Quota " + name + " doesn't allow client supplied keys.", ErrorCodes::QUOTA_DOESNT_ALLOW_KEYS); @@ -228,29 +260,37 @@ QuotaForIntervals & Quota::get(const String & quota_key, const String & user_nam Container::iterator it = quota_for_keys.find(quota_key_hashed); if (quota_for_keys.end() == it) { - it = quota_for_keys.insert(std::make_pair(quota_key_hashed, QuotaForIntervals(this))).first; - it->second = max; + it = quota_for_keys.insert(std::make_pair(quota_key_hashed, new QuotaForIntervals(max))).first; } return it->second; } -void Quotas::initFromConfig() +void Quotas::loadFromConfig(Poco::Util::AbstractConfiguration & config) { - Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config(); - Poco::Util::AbstractConfiguration::Keys config_keys; config.keys("quotas", config_keys); + /// Удалим ключи, которых больше нет в кофиге. + std::set keys_set(config_keys.begin(), config_keys.end()); + for (Container::iterator it = cont.begin(); it != cont.end();) + { + if (keys_set.count(it->first)) + ++it; + else + cont.erase(it++); + } + for (Poco::Util::AbstractConfiguration::Keys::const_iterator it = config_keys.begin(); it != config_keys.end(); ++it) { - cont[*it] = new Quota(); - cont[*it]->initFromConfig("quotas." + *it, *it); + if (!cont[*it]) + cont[*it] = new Quota(); + cont[*it]->loadFromConfig("quotas." + *it, *it, config); } } -QuotaForIntervals & Quotas::get(const String & name, const String & quota_key, const String & user_name, const Poco::Net::IPAddress & ip) +QuotaForIntervalsPtr Quotas::get(const String & name, const String & quota_key, const String & user_name, const Poco::Net::IPAddress & ip) { Container::iterator it = cont.find(name); if (cont.end() == it) diff --git a/dbms/src/Interpreters/Settings.cpp b/dbms/src/Interpreters/Settings.cpp index 0dfa5c39c76..f0d51dec4b5 100644 --- a/dbms/src/Interpreters/Settings.cpp +++ b/dbms/src/Interpreters/Settings.cpp @@ -41,7 +41,6 @@ void Settings::set(const String & name, const Field & value) else if (name == "extremes") extremes = safeGet(value); else if (name == "use_uncompressed_cache") use_uncompressed_cache = safeGet(value); else if (name == "use_splitting_aggregator") use_splitting_aggregator = safeGet(value); - else if (name == "profile") setProfile(get(value)); else if (name == "load_balancing") load_balancing = getLoadBalancing(safeGet(value)); else if (name == "default_sample") { @@ -85,12 +84,6 @@ void Settings::set(const String & name, ReadBuffer & buf) readVarUInt(value, buf); set(name, value); } - else if (name == "profile") - { - String value; - readBinary(value, buf); - setProfile(value); - } else if (name == "load_balancing") { String value; @@ -138,17 +131,12 @@ void Settings::set(const String & name, const String & value) { set(name, Field(value)); } - else if (name == "profile") - { - setProfile(value); - } else if (!limits.trySet(name, value)) throw Exception("Unknown setting " + name, ErrorCodes::UNKNOWN_SETTING); } -void Settings::setProfile(const String & profile_name) +void Settings::setProfile(const String & profile_name, Poco::Util::AbstractConfiguration & config) { - Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config(); String elem = "profiles." + profile_name; if (!config.has(elem)) diff --git a/dbms/src/Server/HTTPHandler.cpp b/dbms/src/Server/HTTPHandler.cpp index 1c8d9a76aa4..70b3976dcf3 100644 --- a/dbms/src/Server/HTTPHandler.cpp +++ b/dbms/src/Server/HTTPHandler.cpp @@ -109,7 +109,7 @@ void HTTPHandler::processQuery(Poco::Net::HTTPServerRequest & request, Poco::Net { } else /// Все неизвестные параметры запроса рассматриваются, как настройки. - context.getSettingsRef().set(it->first, it->second); + context.setSetting(it->first, it->second); } if (readonly) diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index d9bdc6e08cb..362c2f7f240 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -82,6 +83,88 @@ public: }; +UsersConfigReloader::UsersConfigReloader(const std::string & path_, Poco::SharedPtr context_) + : path(path_), context(context_), file_modification_time(0), quit(false), log(&Logger::get("UsersConfigReloader")) +{ + reloadIfNewer(true); + thread = std::thread(&UsersConfigReloader::run, this); +} + +UsersConfigReloader::~UsersConfigReloader() +{ + try + { + quit = true; + thread.join(); + } + catch(...) + { + tryLogCurrentException("~UsersConfigReloader"); + } +} + +void UsersConfigReloader::run() +{ + while (!quit) + { + std::this_thread::sleep_for(std::chrono::seconds(2)); + reloadIfNewer(false); + } +} + +void UsersConfigReloader::reloadIfNewer(bool force) +{ + Poco::File f(path); + time_t new_modification_time = f.getLastModified().epochTime(); + if (!force && new_modification_time == file_modification_time) + return; + file_modification_time = new_modification_time; + + LOG_DEBUG(log, "Loading users config"); + + ConfigurationPtr config; + + try + { + config = new Poco::Util::XMLConfiguration(path); + } + catch (Poco::Exception & e) + { + if (force) + throw; + + LOG_ERROR(log, "Couldn't parse users config: " << e.what() << ", " << e.displayText()); + return; + } + + try + { + context->setUsersConfig(config); + } + catch (Exception & e) + { + if (force) + throw; + + LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText() << "\n" << e.getStackTrace().toString()); + } + catch (Poco::Exception & e) + { + if (force) + throw; + + LOG_ERROR(log, "Error updating users config: " << e.what() << ": " << e.displayText()); + } + catch (...) + { + if (force) + throw; + + LOG_ERROR(log, "Error updating users config."); + } +} + + int Server::main(const std::vector & args) { Logger * log = &logger(); @@ -99,11 +182,8 @@ int Server::main(const std::vector & args) global_context->setGlobalContext(*global_context); global_context->setPath(config.getString("path")); - /// Загружаем пользователей. - global_context->initUsersFromConfig(); - - /// Загружаем квоты. - global_context->initQuotasFromConfig(); + std::string users_config_path = config.getString("users_config", "users.xml"); + users_config_reloader = new UsersConfigReloader(users_config_path, global_context); /// Максимальное количество одновременно выполняющихся запросов. global_context->getProcessList().setMaxSize(config.getInt("max_concurrent_queries", 0)); @@ -120,7 +200,7 @@ int Server::main(const std::vector & args) /// Загружаем настройки. Settings & settings = global_context->getSettingsRef(); - settings.setProfile(config.getString("default_profile", "default")); + global_context->setSetting("profile", config.getString("default_profile", "default")); LOG_INFO(log, "Loading metadata."); loadMetadata(*global_context); @@ -198,6 +278,9 @@ int Server::main(const std::vector & args) waitForTerminationRequest(); LOG_DEBUG(log, "Received termination signal. Waiting for current connections to close."); + + users_config_reloader = NULL; + is_cancelled = true; http_server.stop(); diff --git a/dbms/src/Server/Server.h b/dbms/src/Server/Server.h index 14611787ddb..26da019d77d 100644 --- a/dbms/src/Server/Server.h +++ b/dbms/src/Server/Server.h @@ -25,6 +25,9 @@ #include "OLAPQueryParser.h" #include "OLAPQueryConverter.h" +#include +#include + /** Сервер предоставляет три интерфейса: * 1. HTTP - простой интерфейс для доступа из любых приложений. * 2. TCP - интерфейс для доступа из родной библиотеки, родного клиента, и для межсерверного взаимодействия. @@ -40,6 +43,27 @@ namespace DB { +/// Каждые две секунды проверяет, не изменился ли конфиг. Когда изменился, вызывает setUsersConfig у контекста. +class UsersConfigReloader +{ +public: + UsersConfigReloader(const std::string & path, Poco::SharedPtr context); + ~UsersConfigReloader(); +private: + std::string path; + Poco::SharedPtr context; + + time_t file_modification_time; + std::atomic quit; + std::thread thread; + + Logger * log; + + void reloadIfNewer(bool force); + void run(); +}; + + class Server : public Daemon { public: @@ -54,6 +78,8 @@ public: Server() : config(Application::instance().config()) {} protected: + Poco::SharedPtr users_config_reloader; + void initialize(Application& self) { Daemon::initialize(self);