diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index a3050396608..7add9d98627 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -3988,6 +3988,11 @@ Plug-in Configuration Specifies the location of |TS| plugins. +.. ts:cv:: CONFIG proxy.config.plugin.dynamic_reload_mode INT 1 + + Enables (``1``) or disables (``0``) the dynamic reload feature for remap + plugins (`remap.config`). Global plugins (`plugin.config`) do not have dynamic reload feature yet. + SOCKS Processor =============== diff --git a/doc/developer-guide/design-documents/reloading-plugins.en.rst b/doc/developer-guide/design-documents/reloading-plugins.en.rst index 7d573410546..fb0d2ffee9b 100644 --- a/doc/developer-guide/design-documents/reloading-plugins.en.rst +++ b/doc/developer-guide/design-documents/reloading-plugins.en.rst @@ -30,6 +30,8 @@ configuration reload, i.e.:: traffic_ctl config reload +This feature is enabled by default. It can be turned off by setting the configuration variable :ts:cv:`proxy.config.plugin.dynamic_reload_mode` to ``0`` in :file:`records.config`. When the feature is turned off, once ATS is started one will be able load only one version of a plugin, and re-loading the same plugin would do nothing. + Although plugin reloading should be transparent to plugin developers, the following are some design considerations and implementation details for this feature. @@ -175,3 +177,7 @@ be reused by 'global' plugins in the future. To make sure plugins are still loaded while their code is still in use there is reference counting done around ``PluginDso`` which inherits ``RefCountObj`` and implements ``acquire()`` and ``release()`` methods which are called by ``TSCreateCont``, ``TSVConnCreate`` and ``TSDestroyCont``. + +Other notes +----------- +When this feature for dynamic plugin reload is turned on (:ts:cv:`proxy.config.plugin.dynamic_reload_mode` is set to ``1``), there is one pitfall users should be aware of. Since "global" plugins do not support this feature while the "remap" plugins do, if a plugin is used as a global plugin as well as a remap plugin, there will be two different copies of the plugin loaded in memory with no state shared between them. diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc index 97f72afd182..dc07c4461a7 100644 --- a/mgmt/RecordsConfig.cc +++ b/mgmt/RecordsConfig.cc @@ -1050,6 +1050,8 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.url_remap.pristine_host_hdr", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} , + {RECT_CONFIG, "proxy.config.plugin.dynamic_reload_mode", RECD_INT, "1", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} + , //############################################################################## //# diff --git a/proxy/Plugin.cc b/proxy/Plugin.cc index 79b3b086405..3094743ef5f 100644 --- a/proxy/Plugin.cc +++ b/proxy/Plugin.cc @@ -34,6 +34,33 @@ #define MAX_PLUGIN_ARGS 64 +static PluginDynamicReloadMode plugin_dynamic_reload_mode = REMAP_PLUGIN_DYNAMIC_RELOAD_ON; + +bool +isPluginDynamicReloadEnabled() +{ + return plugin_dynamic_reload_mode == REMAP_PLUGIN_DYNAMIC_RELOAD_ON; +} + +void +parsePluginDynamicReloadConfig() +{ + int int_plugin_dynamic_reload_mode; + REC_ReadConfigInteger(int_plugin_dynamic_reload_mode, "proxy.config.plugin.dynamic_reload_mode"); + plugin_dynamic_reload_mode = (PluginDynamicReloadMode)int_plugin_dynamic_reload_mode; + if (plugin_dynamic_reload_mode < PLUGIN_DYNAMIC_RELOAD_MODE_MIN || plugin_dynamic_reload_mode > PLUGIN_DYNAMIC_RELOAD_MODE_MAX) { + Warning("proxy.config.plugin.dynamic_reload_mode out of range. using default value."); + plugin_dynamic_reload_mode = REMAP_PLUGIN_DYNAMIC_RELOAD_ON; + } + Note("Initialized plugin_dynamic_reload_mode: %d", plugin_dynamic_reload_mode); +} + +void +parsePluginConfig() +{ + parsePluginDynamicReloadConfig(); +} + static const char *plugin_dir = "."; using init_func_t = void (*)(int, char **); diff --git a/proxy/Plugin.h b/proxy/Plugin.h index 8c445a9f36c..8bca36407d5 100644 --- a/proxy/Plugin.h +++ b/proxy/Plugin.h @@ -26,6 +26,18 @@ #include #include "tscore/List.h" +enum PluginDynamicReloadMode { + PLUGIN_DYNAMIC_RELOAD_MODE_MIN = 0, + PLUGIN_DYNAMIC_RELOAD_OFF = 0, + REMAP_PLUGIN_DYNAMIC_RELOAD_ON = 1, + PLUGIN_DYNAMIC_RELOAD_MODE_MAX = 1 +}; + +// read records.config to parse plugin related configs +void parsePluginConfig(); + +bool isPluginDynamicReloadEnabled(); + struct PluginRegInfo { PluginRegInfo(); ~PluginRegInfo(); diff --git a/proxy/http/remap/PluginDso.cc b/proxy/http/remap/PluginDso.cc index 7bd4dcf71af..eb4a895258c 100644 --- a/proxy/http/remap/PluginDso.cc +++ b/proxy/http/remap/PluginDso.cc @@ -41,6 +41,8 @@ PluginDso::PluginDso(const fs::path &configPath, const fs::path &effectivePath, const fs::path &runtimePath) : _configPath(configPath), _effectivePath(effectivePath), _runtimePath(runtimePath) { + PluginDebug(_tag, "PluginDso (%p) created _configPath: [%s] _effectivePath: [%s] _runtimePath: [%s]", this, _configPath.c_str(), + _effectivePath.c_str(), _runtimePath.c_str()); } PluginDso::~PluginDso() @@ -71,9 +73,9 @@ PluginDso::load(std::string &error) } else { PluginDebug(_tag, "plugin '%s' effective path: %s", _configPath.c_str(), _effectivePath.c_str()); - /* Copy the installed plugin DSO to a runtime directory */ + /* Copy the installed plugin DSO to a runtime directory if dynamic reload enabled */ std::error_code ec; - if (!copy(_effectivePath, _runtimePath, ec)) { + if (isDynamicReloadEnabled() && !copy(_effectivePath, _runtimePath, ec)) { std::string temp_error; temp_error.append("failed to create a copy: ").append(strerror(ec.value())); error.assign(temp_error); @@ -215,6 +217,18 @@ PluginDso::modTime() const return _mtime; } +/** + * @brief file handle returned by dlopen syscall + * + * @return dlopen filehandle + */ + +void * +PluginDso::dlOpenHandle() const +{ + return _dlh; +} + /** * @brief clean files created by the plugin instance and handle errors * @@ -224,6 +238,10 @@ PluginDso::modTime() const void PluginDso::clean(std::string &error) { + if (!isDynamicReloadEnabled()) { + return; + } + if (false == remove(_runtimePath, _errorCode)) { error.append("failed to remove runtime copy: ").append(_errorCode.message()); } @@ -266,6 +284,12 @@ PluginDso::instanceCount() return _instanceCount.refcount(); } +bool +PluginDso::isDynamicReloadEnabled() const +{ + return (_runtimePath != _effectivePath); +} + void PluginDso::LoadedPlugins::add(PluginDso *plugin) { @@ -283,8 +307,13 @@ PluginDso::LoadedPlugins::remove(PluginDso *plugin) this_ethread()->schedule_imm(new DeleterContinuation(plugin)); } +/* check if need to reload the plugin DSO + * if dynamic reload not enabled: check if plugin Dso with same effective path aready loaded + * if dynamic reload enabled: check if plugin Dso with same effective path and same time stamp already loaded + * return pointer to already loaded plugin if found, else return null + */ PluginDso * -PluginDso::LoadedPlugins::findByEffectivePath(const fs::path &path) +PluginDso::LoadedPlugins::findByEffectivePath(const fs::path &path, bool dynamicReloadEnabled) { struct stat sb; time_t mtime = 0; @@ -295,7 +324,8 @@ PluginDso::LoadedPlugins::findByEffectivePath(const fs::path &path) SCOPED_MUTEX_LOCK(lock, _mutex, this_ethread()); auto spot = std::find_if(_list.begin(), _list.end(), [&](PluginDso const &plugin) -> bool { - return (0 == path.string().compare(plugin.effectivePath().string()) && (mtime == plugin.modTime())); + return ((!dynamicReloadEnabled || (mtime == plugin.modTime())) && + (0 == path.string().compare(plugin.effectivePath().string()))); }); return spot == _list.end() ? nullptr : static_cast(spot); } diff --git a/proxy/http/remap/PluginDso.h b/proxy/http/remap/PluginDso.h index 47fc0801470..be43803499d 100644 --- a/proxy/http/remap/PluginDso.h +++ b/proxy/http/remap/PluginDso.h @@ -43,6 +43,8 @@ namespace fs = ts::file; #include "I_EventSystem.h" #include "tscpp/util/IntrusiveDList.h" +#include "Plugin.h" + class PluginThreadContext : public RefCountObj { public: @@ -69,6 +71,7 @@ class PluginDso : public PluginThreadContext const fs::path &effectivePath() const; const fs::path &runtimePath() const; time_t modTime() const; + void *dlOpenHandle() const; /* List used by the plugin factory */ using self_type = PluginDso; ///< Self reference type. @@ -89,6 +92,7 @@ class PluginDso : public PluginThreadContext void incInstanceCount(); void decInstanceCount(); int instanceCount(); + bool isDynamicReloadEnabled() const; class LoadedPlugins : public RefCountObj { @@ -96,7 +100,7 @@ class PluginDso : public PluginThreadContext LoadedPlugins() : _mutex(new_ProxyMutex()) {} void add(PluginDso *plugin); void remove(PluginDso *plugin); - PluginDso *findByEffectivePath(const fs::path &path); + PluginDso *findByEffectivePath(const fs::path &path, bool dynamicReloadEnabled); void indicatePreReload(const char *factoryId); void indicatePostReload(bool reloadSuccessful, const std::unordered_map &pluginUsed, const char *factoryId); diff --git a/proxy/http/remap/PluginFactory.cc b/proxy/http/remap/PluginFactory.cc index 6773384326d..0cb5dfbc623 100644 --- a/proxy/http/remap/PluginFactory.cc +++ b/proxy/http/remap/PluginFactory.cc @@ -141,7 +141,7 @@ PluginFactory::getUuid() * @return pointer to a plugin instance, nullptr if failure */ RemapPluginInst * -PluginFactory::getRemapPlugin(const fs::path &configPath, int argc, char **argv, std::string &error) +PluginFactory::getRemapPlugin(const fs::path &configPath, int argc, char **argv, std::string &error, bool dynamicReloadEnabled) { /* Discover the effective path by looking into the search dirs */ fs::path effectivePath = getEffectivePath(configPath); @@ -153,7 +153,7 @@ PluginFactory::getRemapPlugin(const fs::path &configPath, int argc, char **argv, } /* Only one plugin with this effective path can be loaded by a plugin factory */ - RemapPluginInfo *plugin = dynamic_cast(findByEffectivePath(effectivePath)); + RemapPluginInfo *plugin = dynamic_cast(findByEffectivePath(effectivePath, dynamicReloadEnabled)); RemapPluginInst *inst = nullptr; if (nullptr == plugin) { @@ -161,13 +161,23 @@ PluginFactory::getRemapPlugin(const fs::path &configPath, int argc, char **argv, PluginDebug(_tag, "plugin '%s' has not been loaded yet, loading as remap plugin", configPath.c_str()); fs::path runtimePath; - runtimePath /= _runtimeDir; - runtimePath /= effectivePath.relative_path(); - fs::path parent = runtimePath.parent_path(); - if (!fs::create_directories(parent, _ec)) { - error.assign("failed to create plugin runtime dir"); - return nullptr; + // if dynamic reload enabled then create a temporary location to copy .so and load from there + // else load from original location + if (dynamicReloadEnabled) { + runtimePath /= _runtimeDir; + runtimePath /= effectivePath.relative_path(); + + fs::path parent = runtimePath.parent_path(); + PluginDebug(_tag, "Using effectivePath: [%s] runtimePath: [%s] parent: [%s]", effectivePath.c_str(), runtimePath.c_str(), + parent.c_str()); + if (!fs::create_directories(parent, _ec)) { + error.assign("failed to create plugin runtime dir"); + return nullptr; + } + } else { + runtimePath = effectivePath; + PluginDebug(_tag, "Using effectivePath: [%s] runtimePath: [%s]", effectivePath.c_str(), runtimePath.c_str()); } plugin = new RemapPluginInfo(configPath, effectivePath, runtimePath); @@ -187,7 +197,7 @@ PluginFactory::getRemapPlugin(const fs::path &configPath, int argc, char **argv, delete plugin; } - if (_preventiveCleaning) { + if (dynamicReloadEnabled && _preventiveCleaning) { clean(error); } } else { @@ -244,9 +254,9 @@ PluginFactory::getEffectivePath(const fs::path &configPath) * @return plugin found or nullptr if not found */ PluginDso * -PluginFactory::findByEffectivePath(const fs::path &path) +PluginFactory::findByEffectivePath(const fs::path &path, bool dynamicReloadEnabled) { - return PluginDso::loadedPlugins()->findByEffectivePath(path); + return PluginDso::loadedPlugins()->findByEffectivePath(path, dynamicReloadEnabled); } /** diff --git a/proxy/http/remap/PluginFactory.h b/proxy/http/remap/PluginFactory.h index 3c2902ced84..4f6ee6d3589 100644 --- a/proxy/http/remap/PluginFactory.h +++ b/proxy/http/remap/PluginFactory.h @@ -94,7 +94,7 @@ class PluginFactory PluginFactory &setRuntimeDir(const fs::path &runtimeDir); PluginFactory &addSearchDir(const fs::path &searchDir); - RemapPluginInst *getRemapPlugin(const fs::path &configPath, int argc, char **argv, std::string &error); + RemapPluginInst *getRemapPlugin(const fs::path &configPath, int argc, char **argv, std::string &error, bool dynamicReloadEnabled); virtual const char *getUuid(); void clean(std::string &error); @@ -104,7 +104,7 @@ class PluginFactory void indicatePostReload(bool reloadSuccessful); protected: - PluginDso *findByEffectivePath(const fs::path &path); + PluginDso *findByEffectivePath(const fs::path &path, bool dynamicReloadEnabled); fs::path getEffectivePath(const fs::path &configPath); std::vector _searchDirs; /** @brief ordered list of search paths where we look for plugins */ diff --git a/proxy/http/remap/RemapConfig.cc b/proxy/http/remap/RemapConfig.cc index ecba86328b9..fa0acd52edd 100644 --- a/proxy/http/remap/RemapConfig.cc +++ b/proxy/http/remap/RemapConfig.cc @@ -817,7 +817,8 @@ remap_load_plugin(const char **argv, int argc, url_mapping *mp, char *errbuf, in REC_ReadConfigInteger(elevate_access, "proxy.config.plugin.load_elevated"); ElevateAccess access(elevate_access ? ElevateAccess::FILE_PRIVILEGE : 0); - pi = rewrite->pluginFactory.getRemapPlugin(ts::file::path(const_cast(c)), parc, pargv, error); + pi = rewrite->pluginFactory.getRemapPlugin(ts::file::path(const_cast(c)), parc, pargv, error, + isPluginDynamicReloadEnabled()); } // done elevating access bool result = true; diff --git a/proxy/http/remap/unit-tests/plugin_testing_common.cc b/proxy/http/remap/unit-tests/plugin_testing_common.cc index a54422c184a..c2d329546fb 100644 --- a/proxy/http/remap/unit-tests/plugin_testing_common.cc +++ b/proxy/http/remap/unit-tests/plugin_testing_common.cc @@ -50,3 +50,24 @@ getTemporaryDir() return fs::path(mkdtemp(dirNameTemplate)); } + +// implement functions to support unit-testing of option to enable/disable dynamic reload of plugins +static PluginDynamicReloadMode plugin_dynamic_reload_mode = REMAP_PLUGIN_DYNAMIC_RELOAD_ON; + +bool +isPluginDynamicReloadEnabled() +{ + return plugin_dynamic_reload_mode == REMAP_PLUGIN_DYNAMIC_RELOAD_ON; +} + +void +enablePluginDynamicReload() +{ + plugin_dynamic_reload_mode = REMAP_PLUGIN_DYNAMIC_RELOAD_ON; +} + +void +disablePluginDynamicReload() +{ + plugin_dynamic_reload_mode = PLUGIN_DYNAMIC_RELOAD_OFF; +} diff --git a/proxy/http/remap/unit-tests/plugin_testing_common.h b/proxy/http/remap/unit-tests/plugin_testing_common.h index fd01dd05683..1669a303f02 100644 --- a/proxy/http/remap/unit-tests/plugin_testing_common.h +++ b/proxy/http/remap/unit-tests/plugin_testing_common.h @@ -101,3 +101,7 @@ void PrintToStdErr(const char *fmt, ...); #ifdef __cplusplus } #endif /* __cplusplus */ + +// functions to support unit-testing of option to enable/disable dynamic reload of plugins +void enablePluginDynamicReload(); +void disablePluginDynamicReload(); \ No newline at end of file diff --git a/proxy/http/remap/unit-tests/test_PluginFactory.cc b/proxy/http/remap/unit-tests/test_PluginFactory.cc index de80397c504..faa7e6fa813 100644 --- a/proxy/http/remap/unit-tests/test_PluginFactory.cc +++ b/proxy/http/remap/unit-tests/test_PluginFactory.cc @@ -114,6 +114,78 @@ clean() fs::remove(sandboxDir, ec); } +static int +getPluginVersion(const PluginDso &plugin) +{ + std::string error; + void *s = nullptr; + CHECK(plugin.getSymbol("pluginDsoVersionTest", s, error)); + int (*version)() = reinterpret_cast(s); + return version ? version() : -1; +} + +// this is a simple class to simulate loading of Plugin Dso as done during loading of global plugins +class GlobalPluginInfo +{ +public: + GlobalPluginInfo() : _dlh(nullptr){}; + ~GlobalPluginInfo(){}; + + bool + loadDso(const fs::path &configPath) + { + CHECK(fs::exists(configPath)); + + void *handle = dlopen(configPath.c_str(), RTLD_NOW); + if (!handle) { + return false; + } + + _dlh = handle; + return true; + } + + bool + getSymbol(const char *symbol, void *&address, std::string &error) const + { + /* Clear the errors */ + dlerror(); + error.clear(); + + address = dlsym(_dlh, symbol); + char *err = dlerror(); + + if (nullptr == address && nullptr != err) { + /* symbol really cannot be found */ + error.assign(err); + return false; + } + + return true; + } + + void * + dlOpenHandle() + { + return _dlh; + } + + int + getPluginVersion() + { + std::string error; + void *s = nullptr; + CHECK(getSymbol("pluginDsoVersionTest", s, error)); + auto version = reinterpret_cast(s); + return version ? version() : -1; + } + +private: + void *_dlh; +}; + +// copies the .so file (pluginBuildPath) in the target directory (effectivePath) +// as the desired .so filename (configPath) with the desired timestamp (mtime) static void setupConfigPathTest(const fs::path &configPath, const fs::path &pluginBuildPath, const fs::path &uuid, fs::path &effectivePath, fs::path &runtimePath, time_t mtime = 0, bool append = false) @@ -124,7 +196,11 @@ setupConfigPathTest(const fs::path &configPath, const fs::path &pluginBuildPath, } effectivePath = configPath.is_absolute() ? configPath : searchDir / configPath; - runtimePath = runtimeRootDir / uuid / effectivePath.relative_path(); + if (isPluginDynamicReloadEnabled()) { + runtimePath = runtimeRootDir / uuid / effectivePath.relative_path(); + } else { + runtimePath = effectivePath; + } /* Create the directory structure and install plugins */ fs::create_directories(effectivePath.parent_path(), ec); @@ -175,6 +251,7 @@ SCENARIO("loading plugins", "[plugin][core]") fs::path effectivePath; fs::path runtimePath; std::string error; + enablePluginDynamicReload(); GIVEN("an existing plugin") { @@ -188,12 +265,12 @@ SCENARIO("loading plugins", "[plugin][core]") setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath); PluginFactoryUnitTest *factory = getFactory(tempComponent); - RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error); + RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("expect it to successfully load") { validateSuccessfulConfigPathTest(plugin, error, effectivePath, runtimePath); - CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath)); + CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath, isPluginDynamicReloadEnabled())); } teardownConfigPathTest(factory); @@ -206,35 +283,63 @@ SCENARIO("loading plugins", "[plugin][core]") setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath); PluginFactoryUnitTest *factory = getFactory(tempComponent); - RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error); + RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("expect it to successfully load") { validateSuccessfulConfigPathTest(plugin, error, effectivePath, runtimePath); - CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath)); + CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath, isPluginDynamicReloadEnabled())); } teardownConfigPathTest(factory); } - WHEN("config is using plugin absolute path") + WHEN("config is using plugin absolute path - dynamic reload is ENABLED") { fs::path configPath = searchDir / "subdir" / pluginName; CHECK(configPath.is_absolute()); /* make sure this is absolute path - this is what we are testing */ setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath); PluginFactoryUnitTest *factory = getFactory(tempComponent); - RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error); + RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("expect it to successfully load") { validateSuccessfulConfigPathTest(plugin, error, effectivePath, runtimePath); - CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath)); + CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath, isPluginDynamicReloadEnabled())); + + // check Dso at effective path still exists while copy at runtime path doesn't + CHECK(fs::exists(plugin->_plugin.effectivePath())); + CHECK(!fs::exists(plugin->_plugin.runtimePath())); } teardownConfigPathTest(factory); } + WHEN("config is using plugin absolute path - dynamic reload is DISABLED") + { + disablePluginDynamicReload(); + fs::path configPath = searchDir / "subdir" / pluginName; + CHECK(configPath.is_absolute()); /* make sure this is absolute path - this is what we are testing */ + + setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath); + PluginFactoryUnitTest *factory = getFactory(tempComponent); + RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); + + THEN("expect it to successfully load") + { + validateSuccessfulConfigPathTest(plugin, error, effectivePath, runtimePath); + CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath, isPluginDynamicReloadEnabled())); + + // check Dso still exists + CHECK(plugin->_plugin.effectivePath() == plugin->_plugin.runtimePath()); + CHECK(fs::exists(plugin->_plugin.effectivePath())); + } + + teardownConfigPathTest(factory); + enablePluginDynamicReload(); + } + WHEN("config using nonexisting relative plugin file name") { fs::path relativeExistingPath = pluginName; @@ -245,7 +350,7 @@ SCENARIO("loading plugins", "[plugin][core]") setupConfigPathTest(relativeExistingPath, buildPath, tempComponent, effectivePath, runtimePath); PluginFactoryUnitTest *factory = getFactory(tempComponent); - RemapPluginInst *plugin = factory->getRemapPlugin(relativeNonexistingPath, 0, nullptr, error); + RemapPluginInst *plugin = factory->getRemapPlugin(relativeNonexistingPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("expect it to fail with appropriate error message") { @@ -267,7 +372,7 @@ SCENARIO("loading plugins", "[plugin][core]") setupConfigPathTest(relativeExistingPath, buildPath, tempComponent, effectivePath, runtimePath); PluginFactoryUnitTest *factory = getFactory(tempComponent); - RemapPluginInst *plugin = factory->getRemapPlugin(absoluteNonexistingPath, 0, nullptr, error); + RemapPluginInst *plugin = factory->getRemapPlugin(absoluteNonexistingPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("expect it to fail with appropriate error message") { @@ -286,12 +391,12 @@ SCENARIO("loading plugins", "[plugin][core]") fs::path buildPath = pluginBuildDir / configPath; setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath); PluginFactoryUnitTest *factory = getFactory(tempComponent); - RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error); + RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("expect it to unload the plugin dso") { CHECK(nullptr == plugin); - CHECK(nullptr == PluginDso::loadedPlugins()->findByEffectivePath(effectivePath)); + CHECK(nullptr == PluginDso::loadedPlugins()->findByEffectivePath(effectivePath, isPluginDynamicReloadEnabled())); } teardownConfigPathTest(factory); @@ -303,12 +408,12 @@ SCENARIO("loading plugins", "[plugin][core]") fs::path buildPath = pluginBuildDir / configPath; setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath); PluginFactoryUnitTest *factory = getFactory(tempComponent); - RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error); + RemapPluginInst *plugin = factory->getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("expect it to unload the plugin dso") { CHECK(nullptr == plugin); - CHECK(nullptr == PluginDso::loadedPlugins()->findByEffectivePath(effectivePath)); + CHECK(nullptr == PluginDso::loadedPlugins()->findByEffectivePath(effectivePath, isPluginDynamicReloadEnabled())); } teardownConfigPathTest(factory); @@ -319,6 +424,7 @@ SCENARIO("loading plugins", "[plugin][core]") SCENARIO("multiple search dirs + multiple or no plugins installed", "[plugin][core]") { REQUIRE_FALSE(sandboxDir.empty()); + enablePluginDynamicReload(); GIVEN("multiple search dirs specified for the plugin search") { @@ -368,7 +474,7 @@ SCENARIO("multiple search dirs + multiple or no plugins installed", "[plugin][co CHECK(fs::exists(abEffectivePath)); /* Now use an absolute path containing the unregistered search directory */ - RemapPluginInst *pluginInst = factory.getRemapPlugin(abEffectivePath, 0, nullptr, error); + RemapPluginInst *pluginInst = factory.getRemapPlugin(abEffectivePath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("Expect it to successfully load") { @@ -382,7 +488,7 @@ SCENARIO("multiple search dirs + multiple or no plugins installed", "[plugin][co WHEN("a valid plugin is found in the first search path") { - RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error); + RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("Expect it to successfully load the one found in the first search dir and copy it in the runtime dir") { @@ -397,7 +503,7 @@ SCENARIO("multiple search dirs + multiple or no plugins installed", "[plugin][co WHEN("the first search dir is missing the plugin but the second search has it") { CHECK(fs::remove(effectivePath1, ec)); - RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error); + RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("Expect it to successfully load the one found in the second search dir") { @@ -413,7 +519,7 @@ SCENARIO("multiple search dirs + multiple or no plugins installed", "[plugin][co { CHECK(fs::remove(effectivePath1, ec)); CHECK(fs::remove(effectivePath2, ec)); - RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error); + RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); THEN("Expect it to successfully load the one found in the third search dir") { @@ -433,7 +539,7 @@ SCENARIO("multiple search dirs + multiple or no plugins installed", "[plugin][co THEN("expect the plugin load to fail.") { - RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error); + RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error, isPluginDynamicReloadEnabled()); CHECK(nullptr == pluginInst); CHECK(std::string("failed to find plugin '").append(configPath.string()).append("'") == error); CHECK_FALSE(fs::exists(runtimePath1)); @@ -445,30 +551,103 @@ SCENARIO("multiple search dirs + multiple or no plugins installed", "[plugin][co } } -static int -getPluginVersion(const PluginDso &plugin) +void +checkTwoLoadedVersionsDifferent(const RemapPluginInst *plugin_v1, const RemapPluginInst *plugin_v2) +{ + void *tsRemapInitSym_v1_t2 = nullptr; /* callback address from DSO v1 at moment t2 */ + void *tsRemapInitSym_v2_t2 = nullptr; /* callback address from DSO v2 at moment t2 */ + std::string error; + + /* Make sure we ended up with different DSO objects and runtime paths are different - new plugin was indeed loaded */ + CHECK(&(plugin_v1->_plugin) != &(plugin_v2->_plugin)); + CHECK(plugin_v1->_plugin.runtimePath() != plugin_v2->_plugin.runtimePath()); + CHECK(plugin_v1->_plugin.dlOpenHandle() != plugin_v2->_plugin.dlOpenHandle()); + + /* Make sure what we installed and loaded first was v1 and after the plugin reload we run v2 */ + CHECK(1 == getPluginVersion(plugin_v1->_plugin)); + CHECK(2 == getPluginVersion(plugin_v2->_plugin)); + + /* Make sure the symbols we get from the 2 loaded plugins don't yield the same callback function pointer */ + plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); + plugin_v2->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v2_t2, error); + CHECK(nullptr != tsRemapInitSym_v1_t2); + CHECK(nullptr != tsRemapInitSym_v2_t2); + CHECK(tsRemapInitSym_v1_t2 != tsRemapInitSym_v2_t2); + + // 2 versions can be different only when dynamic reload enabled + CHECK(isPluginDynamicReloadEnabled()); + + // check Dso at effective path still exists while the copy at runtime path doesn't + CHECK(plugin_v1->_plugin.effectivePath() != plugin_v1->_plugin.runtimePath()); + CHECK(fs::exists(plugin_v1->_plugin.effectivePath())); + CHECK(!fs::exists(plugin_v1->_plugin.runtimePath())); + CHECK(plugin_v2->_plugin.effectivePath() != plugin_v2->_plugin.runtimePath()); + CHECK(fs::exists(plugin_v2->_plugin.effectivePath())); + CHECK(!fs::exists(plugin_v2->_plugin.runtimePath())); +} + +void +checkTwoLoadedVersionsSame(RemapPluginInst *plugin_v1, RemapPluginInst *plugin_v2) { + void *tsRemapInitSym_v1_t2 = nullptr; /* callback address from DSO v1 at moment t2 */ + void *tsRemapInitSym_v2_t2 = nullptr; /* callback address from DSO v2 at moment t2 */ std::string error; - void *s = nullptr; - CHECK(plugin.getSymbol("pluginDsoVersionTest", s, error)); - int (*version)() = reinterpret_cast(s); - return version ? version() : -1; + + /* Make sure we ended up with the same DSO object and runtime paths should be same - no new plugin was loaded */ + CHECK(&(plugin_v1->_plugin) == &(plugin_v2->_plugin)); + CHECK(plugin_v1->_plugin.runtimePath() == plugin_v2->_plugin.runtimePath()); + CHECK(plugin_v1->_plugin.dlOpenHandle() == plugin_v2->_plugin.dlOpenHandle()); + + /* Make sure v2 DSO was NOT really loaded - both instances should return same v1 version */ + CHECK(1 == getPluginVersion(plugin_v1->_plugin)); + CHECK(1 == getPluginVersion(plugin_v2->_plugin)); + + /* Make sure the symbols we get from the 2 loaded plugins yield the same callback function pointer */ + plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); + plugin_v2->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v2_t2, error); + CHECK(nullptr != tsRemapInitSym_v1_t2); + CHECK(nullptr != tsRemapInitSym_v2_t2); + CHECK(tsRemapInitSym_v1_t2 == tsRemapInitSym_v2_t2); + + // 2 versions can be same even when dynamic reload is enabled + // 2 versions must be same when dynamic reload is disabled + // check presence/absence of Dso files + if (!isPluginDynamicReloadEnabled()) { + CHECK(plugin_v1->_plugin.effectivePath() == plugin_v1->_plugin.runtimePath()); + CHECK(fs::exists(plugin_v1->_plugin.effectivePath())); + } else { + CHECK(plugin_v1->_plugin.effectivePath() != plugin_v1->_plugin.runtimePath()); + CHECK(fs::exists(plugin_v1->_plugin.effectivePath())); + CHECK(!fs::exists(plugin_v1->_plugin.runtimePath())); + } +} + +std::tuple +testSetupLoadPlugin(const fs::path &configName, const fs::path &buildPath, const fs::path &uuid, const time_t mtime, + fs::path &effectivePath, fs::path &runtimePath) +{ + std::string error; + setupConfigPathTest(configName, buildPath, uuid, effectivePath, runtimePath, mtime); + auto factory = getFactory(uuid); + auto pluginInst = factory->getRemapPlugin(configName, 0, nullptr, error, isPluginDynamicReloadEnabled()); + + return {pluginInst, factory}; } SCENARIO("loading multiple version of the same plugin at the same time", "[plugin][core]") { REQUIRE_FALSE(sandboxDir.empty()); + enablePluginDynamicReload(); - static fs::path uuid_t1 = fs::path("c71e2bab-90dc-4770-9535-c9304c3de381"); /* UUID at moment t1 */ - static fs::path uuid_t2 = fs::path("c71e2bab-90dc-4770-9535-e7304c3ee732"); /* UUID at moment t2 */ + static fs::path uuid_t1 = fs::path("c71e2bab-90dc-4770-9535-c9304c3de381"); /* UUID at moment t1 */ + static fs::path uuid_t2 = fs::path("c71e2bab-90dc-4770-9535-e7304c3ee732"); /* UUID at moment t2 */ + void *tsRemapInitSym_v1_t1 = nullptr; /* callback address from DSO v1 at moment t1 */ + void *tsRemapInitSym_v1_t2 = nullptr; /* callback address from DSO v1 at moment t2 */ - fs::path effectivePath_v1; /* expected effective path for DSO v1 */ - fs::path effectivePath_v2; /* expected effective path for DSO v2 */ - fs::path runtimePath_v1; /* expected runtime path for DSO v1 */ - fs::path runtimePath_v2; /* expected runtime path for DSO v2 */ - void *tsRemapInitSym_v1_t1 = nullptr; /* callback address from DSO v1 at moment t1 */ - void *tsRemapInitSym_v1_t2 = nullptr; /* callback address from DSO v1 at moment t2 */ - void *tsRemapInitSym_v2_t2 = nullptr; /* callback address from DSO v2 at moment t2 */ + fs::path effectivePath_v1; /* expected effective path for DSO v1 */ + fs::path effectivePath_v2; /* expected effective path for DSO v2 */ + fs::path runtimePath_v1; /* expected runtime path for DSO v1 */ + fs::path runtimePath_v2; /* expected runtime path for DSO v2 */ std::string error; std::string error1; @@ -478,26 +657,26 @@ SCENARIO("loading multiple version of the same plugin at the same time", "[plugi fs::path buildPath_v1 = pluginBuildDir / fs::path("plugin_v1.so"); /* DSO v1 */ fs::path buildPath_v2 = pluginBuildDir / fs::path("plugin_v2.so"); /* DSO v1 */ - GIVEN("two different versions v1 and v2 of same plugin") + GIVEN("two different versions v1 and v2 of same plugin with different time stamps - dynamic plugin reload ENABLED") { WHEN("(1) loading v1, (2) overwriting with v2 and then (3) reloading by using the same plugin name, " "(*) v1 and v2 DSOs modification time are different (changed)") { + enablePluginDynamicReload(); + /* Simulate installing plugin plugin_v1.so (ver 1) as plugin.so and loading it at some point of time t1 */ - setupConfigPathTest(configName, buildPath_v1, uuid_t1, effectivePath_v1, runtimePath_v1, 1556825556); - PluginFactoryUnitTest *factory1 = getFactory(uuid_t1); - RemapPluginInst *plugin_v1 = factory1->getRemapPlugin(configName, 0, nullptr, error1); + auto [plugin_v1, factory1] = + testSetupLoadPlugin(configName, buildPath_v1, uuid_t1, 1556825556, effectivePath_v1, runtimePath_v1); plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t1, error); /* Simulate installing plugin plugin_v2.so (v1) as plugin.so and loading it at some point of time t2 */ /* Note that during the installation plugin_v2.so (v2) is "barberically" overriding the existing plugin.so which was v1 */ - setupConfigPathTest(configName, buildPath_v2, uuid_t2, effectivePath_v2, runtimePath_v2, 1556825557); - PluginFactoryUnitTest *factory2 = getFactory(uuid_t2); - RemapPluginInst *plugin_v2 = factory2->getRemapPlugin(configName, 0, nullptr, error2); + auto [plugin_v2, factory2] = + testSetupLoadPlugin(configName, buildPath_v2, uuid_t2, 1556825557, effectivePath_v2, runtimePath_v2); + plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); /* Make sure plugin.so was overriden */ CHECK(effectivePath_v1 == effectivePath_v2); - /* Although effective path is the same runtime paths should be different */ CHECK(runtimePath_v1 != runtimePath_v2); @@ -507,16 +686,7 @@ SCENARIO("loading multiple version of the same plugin at the same time", "[plugi validateSuccessfulConfigPathTest(plugin_v1, error1, effectivePath_v1, runtimePath_v1); validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v2); - /* Make sure what we installed and loaded first was v1 and after the plugin reload we run v2 */ - CHECK(1 == getPluginVersion(plugin_v1->_plugin)); - CHECK(2 == getPluginVersion(plugin_v2->_plugin)); - - /* Make sure the symbols we get from the 2 loaded plugins don't yield the same callback function pointer */ - plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); - plugin_v2->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v2_t2, error); - CHECK(nullptr != tsRemapInitSym_v1_t2); - CHECK(nullptr != tsRemapInitSym_v2_t2); - CHECK(tsRemapInitSym_v1_t2 != tsRemapInitSym_v2_t2); + checkTwoLoadedVersionsDifferent(plugin_v1, plugin_v2); /* Make sure v1 callback functions addresses did not change for v1 after v2 was loaded */ CHECK(tsRemapInitSym_v1_t1 == tsRemapInitSym_v1_t2); @@ -527,23 +697,25 @@ SCENARIO("loading multiple version of the same plugin at the same time", "[plugi } } - GIVEN("two different versions v1 and v2 of same plugin") + GIVEN("two different versions v1 and v2 of same plugin with same time stamps - dynamic plugin reload ENABLED") { WHEN("(1) loading v1, (2) overwriting with v2 and then (3) reloading by using the same plugin name, " "(*) v1 and v2 DSOs modification time are same (did NOT change)") { + enablePluginDynamicReload(); + /* Simulate installing plugin plugin_v1.so (ver 1) as plugin.so and loading it at some point of time t1 */ - setupConfigPathTest(configName, buildPath_v1, uuid_t1, effectivePath_v1, runtimePath_v1, 1556825556); - PluginFactoryUnitTest *factory1 = getFactory(uuid_t1); - RemapPluginInst *plugin_v1 = factory1->getRemapPlugin(configName, 0, nullptr, error1); + auto [plugin_v1, factory1] = + testSetupLoadPlugin(configName, buildPath_v1, uuid_t1, 1556825556, effectivePath_v1, runtimePath_v1); + plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t1, error); /* Simulate installing plugin plugin_v2.so (v1) as plugin.so and loading it at some point of time t2 */ /* Note that during the installation plugin_v2.so (v2) is "barberically" overriding the existing plugin.so which was v1, since the modification time is exactly the same the new v2 plugin would not be loaded and we should get the same PluginDso address and same effective and runtime paths */ - setupConfigPathTest(configName, buildPath_v2, uuid_t2, effectivePath_v2, runtimePath_v2, 1556825556); - PluginFactoryUnitTest *factory2 = getFactory(uuid_t2); - RemapPluginInst *plugin_v2 = factory2->getRemapPlugin(configName, 0, nullptr, error2); + auto [plugin_v2, factory2] = + testSetupLoadPlugin(configName, buildPath_v2, uuid_t2, 1556825556, effectivePath_v2, runtimePath_v2); + plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); /* Make sure plugin.so was overriden */ CHECK(effectivePath_v1 == effectivePath_v2); @@ -554,24 +726,98 @@ SCENARIO("loading multiple version of the same plugin at the same time", "[plugi validateSuccessfulConfigPathTest(plugin_v1, error1, effectivePath_v1, runtimePath_v1); validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v1); - /* Make sure we ended up with the same DSO object and runtime paths should be same - no new plugin was loaded */ - CHECK(&(plugin_v1->_plugin) == &(plugin_v2->_plugin)); - CHECK(plugin_v1->_plugin.runtimePath() == plugin_v2->_plugin.runtimePath()); + checkTwoLoadedVersionsSame(plugin_v1, plugin_v2); - /* Make sure v2 DSO was NOT loaded both instances should return same v1 version */ - CHECK(1 == getPluginVersion(plugin_v1->_plugin)); - CHECK(1 == getPluginVersion(plugin_v2->_plugin)); + /* Make sure v1 callback functions addresses did not change for v1 after v2 was loaded */ + CHECK(tsRemapInitSym_v1_t1 == tsRemapInitSym_v1_t2); + } - /* Make sure the symbols we get from the 2 loaded plugins yield the same callback function pointer */ - plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); - plugin_v2->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v2_t2, error); - CHECK(nullptr != tsRemapInitSym_v1_t2); - CHECK(nullptr != tsRemapInitSym_v2_t2); - CHECK(tsRemapInitSym_v1_t2 == tsRemapInitSym_v2_t2); + teardownConfigPathTest(factory1); + teardownConfigPathTest(factory2); + } + } + + GIVEN("two different versions v1 and v2 of same plugin with different time stamps - dynamic plugin reload DISABLED") + { + WHEN("(1) loading v1, (2) overwriting with v2 and then (3) reloading by using the same plugin name, " + "(*) v1 and v2 DSOs modification time are different (changed)") + { + disablePluginDynamicReload(); + + /* Simulate installing plugin plugin_v1.so (ver 1) as plugin.so and loading it at some point of time t1 */ + auto [plugin_v1, factory1] = + testSetupLoadPlugin(configName, buildPath_v1, uuid_t1, 1556825556, effectivePath_v1, runtimePath_v1); + plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t1, error); + + /* Simulate installing plugin plugin_v2.so (v1) as plugin.so and loading it at some point of time t2 */ + /* Note that during the installation plugin_v2.so (v2) is "barberically" overriding the existing plugin.so which was v1 */ + auto [plugin_v2, factory2] = + testSetupLoadPlugin(configName, buildPath_v2, uuid_t2, 1556825557, effectivePath_v2, runtimePath_v2); + plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); + + /* Make sure plugin.so was overriden */ + CHECK(effectivePath_v1 == effectivePath_v2); + /* since dynamic reload is disabled, runtimepaths should be same */ + CHECK(runtimePath_v1 == runtimePath_v2); + + THEN("expect v1 plugin to remain loaded since dynamic reload is disabled, even though the timestamp has changed") + { + /* Both getRemapPlugin() calls should succeed but only v1 plugin DSO should be used */ + validateSuccessfulConfigPathTest(plugin_v1, error1, effectivePath_v1, runtimePath_v1); + validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v1); + + checkTwoLoadedVersionsSame(plugin_v1, plugin_v2); + + /* Make sure v1 callback functions addresses did not change for v1 after v2 was loaded */ + CHECK(tsRemapInitSym_v1_t1 == tsRemapInitSym_v1_t2); } teardownConfigPathTest(factory1); teardownConfigPathTest(factory2); + enablePluginDynamicReload(); + } + } + + GIVEN("two different versions v1 and v2 of same plugin with same time stamp - dynamic plugin reload DISABLED") + { + WHEN("(1) loading v1, (2) overwriting with v2 and then (3) reloading by using the same plugin name, " + "(*) v1 and v2 DSOs modification time are same (did NOT change)") + { + disablePluginDynamicReload(); + + /* Simulate installing plugin plugin_v1.so (ver 1) as plugin.so and loading it at some point of time t1 */ + auto [plugin_v1, factory1] = + testSetupLoadPlugin(configName, buildPath_v1, uuid_t1, 1556825556, effectivePath_v1, runtimePath_v1); + plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t1, error); + + /* Simulate installing plugin plugin_v2.so (v1) as plugin.so and loading it at some point of time t2 */ + /* Note that during the installation plugin_v2.so (v2) is "barberically" overriding the existing plugin.so + which was v1, since dynamic reload is disabled the new v2 plugin would not be loaded and + we should get the same PluginDso address and same effective and runtime paths */ + auto [plugin_v2, factory2] = + testSetupLoadPlugin(configName, buildPath_v2, uuid_t2, 1556825556, effectivePath_v2, runtimePath_v2); + plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); + + /* Make sure plugin.so was overriden */ + CHECK(effectivePath_v1 == effectivePath_v2); + /* since dynamic reload is disabled, runtimepaths should be same */ + CHECK(runtimePath_v1 == runtimePath_v2); + + THEN("expect v1 plugin to be loaded since dynamic reload is disabled") + { + /* Both getRemapPlugin() calls should succeed but only v1 plugin DSO should be used */ + validateSuccessfulConfigPathTest(plugin_v1, error1, effectivePath_v1, runtimePath_v1); + validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v1); + + checkTwoLoadedVersionsSame(plugin_v1, plugin_v2); + + /* Make sure v1 callback functions addresses did not change for v1 after v2 was loaded */ + CHECK(tsRemapInitSym_v1_t1 == tsRemapInitSym_v1_t2); + } + + teardownConfigPathTest(factory1); + teardownConfigPathTest(factory2); + enablePluginDynamicReload(); } } @@ -585,9 +831,8 @@ SCENARIO("loading multiple version of the same plugin at the same time", "[plugi PluginFactoryUnitTest *factory1 = getFactory(uuid_t1); /* Now provision and load a plugin using a second factory */ - setupConfigPathTest(configName, buildPath_v2, uuid_t2, effectivePath_v2, runtimePath_v2, 1556825556); - PluginFactoryUnitTest *factory2 = getFactory(uuid_t2); - RemapPluginInst *plugin_v2 = factory2->getRemapPlugin(configName, 0, nullptr, error2); + auto [plugin_v2, factory2] = + testSetupLoadPlugin(configName, buildPath_v2, uuid_t2, 1556825556, effectivePath_v2, runtimePath_v2); THEN("the plugin from the second factory to work") { @@ -603,9 +848,183 @@ SCENARIO("loading multiple version of the same plugin at the same time", "[plugi } } +SCENARIO("loading multiple version of the same plugin in mixed mode - global as well as remap plugin", "[plugin][core]") +{ + REQUIRE_FALSE(sandboxDir.empty()); + enablePluginDynamicReload(); + + static fs::path uuid_t1 = fs::path("c71e2bab-90dc-4770-9535-c9304c3de381"); /* UUID at moment t1 */ + static fs::path uuid_t2 = fs::path("c71e2bab-90dc-4770-9535-e7304c3ee732"); /* UUID at moment t2 */ + + fs::path effectivePath_v1; /* expected effective path for DSO v1 */ + fs::path effectivePath_v2; /* expected effective path for DSO v2 */ + fs::path runtimePath_v1; /* expected runtime path for DSO v1 */ + fs::path runtimePath_v2; /* expected runtime path for DSO v2 */ + void *tsRemapInitSym_v1_t1 = nullptr; /* callback address from DSO v1 at moment t1 */ + void *tsRemapInitSym_v1_t2 = nullptr; /* callback address from DSO v1 at moment t2 */ + void *tsRemapInitSym_v2_t2 = nullptr; /* callback address from DSO v2 at moment t2 */ + + std::string error; + std::string error1; + std::string error2; + + fs::path configName = fs::path("plugin.so"); /* use same config name for all following tests */ + fs::path buildPath_v1 = pluginBuildDir / fs::path("plugin_v1.so"); /* DSO v1 */ + fs::path buildPath_v2 = pluginBuildDir / fs::path("plugin_v2.so"); /* DSO v1 */ + + GIVEN("two different versions v1 and v2 of same plugin in mixed mode - dynamic plugin reload DISABLED") + { + WHEN("(1) loading v1, (2) overwriting with v2 and then (3) reloading by using the same plugin name, " + "(*) v1 and v2 DSOs modification time are different (changed)") + { + disablePluginDynamicReload(); + /* Simulate installing plugin plugin_v1.so (ver 1) as plugin.so and loading it at some point of time t1 */ + setupConfigPathTest(configName, buildPath_v1, uuid_t1, effectivePath_v1, runtimePath_v1, 1556825556); + GlobalPluginInfo global_plugin_v1; + CHECK(global_plugin_v1.loadDso(effectivePath_v1) == true); + global_plugin_v1.getSymbol("TSRemapInit", tsRemapInitSym_v1_t1, error); + + /* Simulate installing plugin plugin_v2.so (v1) as plugin.so and loading it at some point of time t2 */ + /* Note that during the installation plugin_v2.so (v2) is "barberically" overriding the existing plugin.so which was v1 */ + setupConfigPathTest(configName, buildPath_v2, uuid_t2, effectivePath_v2, runtimePath_v2, 1556825557); + PluginFactoryUnitTest *factory2 = getFactory(uuid_t2); + RemapPluginInst *plugin_v2 = factory2->getRemapPlugin(configName, 0, nullptr, error2, isPluginDynamicReloadEnabled()); + + /* Make sure plugin.so was overriden */ + CHECK(effectivePath_v1 == effectivePath_v2); + + /* since dynamic reload is disabled, runtimepaths should be same */ + CHECK(runtimePath_v1 == runtimePath_v2); + + THEN("expect v1 plugin to remain loaded since dynamic reload is disabled, even though the timestamp has changed") + { + validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v2); + + /* Make sure we ended up with the same DSO object and runtime paths should be same - no new plugin was loaded */ + CHECK(global_plugin_v1.dlOpenHandle() == plugin_v2->_plugin.dlOpenHandle()); + + /* Make sure v2 DSO was NOT really loaded - both instances should return same v1 version */ + CHECK(1 == global_plugin_v1.getPluginVersion()); + CHECK(1 == getPluginVersion(plugin_v2->_plugin)); + + /* Make sure the symbols we get from the 2 loaded plugins yield the same callback function pointer */ + global_plugin_v1.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); + plugin_v2->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v2_t2, error); + CHECK(nullptr != tsRemapInitSym_v1_t2); + CHECK(nullptr != tsRemapInitSym_v2_t2); + CHECK(tsRemapInitSym_v1_t2 == tsRemapInitSym_v2_t2); + + /* check that the symbol we got originally is still valid */ + CHECK(tsRemapInitSym_v1_t1 == tsRemapInitSym_v1_t2); + } + + teardownConfigPathTest(factory2); + enablePluginDynamicReload(); + } + } + + GIVEN("two different versions v1 and v2 of same plugin in mixed mode - dynamic plugin reload DISABLED") + { + WHEN("(1) loading v1, (2) overwriting with v2 and then (3) reloading by using the same plugin name, " + "(*) v1 and v2 DSOs modification times are unchanged") + { + disablePluginDynamicReload(); + /* Simulate installing plugin plugin_v1.so (ver 1) as plugin.so and loading it at some point of time t1 */ + setupConfigPathTest(configName, buildPath_v1, uuid_t1, effectivePath_v1, runtimePath_v1, 1556825556); + GlobalPluginInfo global_plugin_v1; + CHECK(global_plugin_v1.loadDso(effectivePath_v1) == true); + global_plugin_v1.getSymbol("TSRemapInit", tsRemapInitSym_v1_t1, error); + + /* Simulate installing plugin plugin_v2.so (v1) as plugin.so and loading it at some point of time t2 */ + /* Note that during the installation plugin_v2.so (v2) is "barberically" overriding the existing plugin.so which was v1 */ + setupConfigPathTest(configName, buildPath_v2, uuid_t2, effectivePath_v2, runtimePath_v2, 1556825556); + PluginFactoryUnitTest *factory2 = getFactory(uuid_t2); + RemapPluginInst *plugin_v2 = factory2->getRemapPlugin(configName, 0, nullptr, error2, isPluginDynamicReloadEnabled()); + + /* Make sure plugin.so was overriden */ + CHECK(effectivePath_v1 == effectivePath_v2); + + /* since dynamic reload is disabled, runtimepaths should be same */ + CHECK(runtimePath_v1 == runtimePath_v2); + + THEN("expect v1 plugin to remain loaded since dynamic reload is disabled, even though the timestamp has changed") + { + validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v2); + + /* Make sure we ended up with the same DSO object and runtime paths should be same - no new plugin was loaded */ + CHECK(global_plugin_v1.dlOpenHandle() == plugin_v2->_plugin.dlOpenHandle()); + + /* Make sure v2 DSO was NOT really loaded - both instances should return same v1 version */ + CHECK(1 == global_plugin_v1.getPluginVersion()); + CHECK(1 == getPluginVersion(plugin_v2->_plugin)); + + /* Make sure the symbols we get from the 2 loaded plugins yield the same callback function pointer */ + global_plugin_v1.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); + plugin_v2->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v2_t2, error); + CHECK(nullptr != tsRemapInitSym_v1_t2); + CHECK(nullptr != tsRemapInitSym_v2_t2); + CHECK(tsRemapInitSym_v1_t2 == tsRemapInitSym_v2_t2); + + /* check that the symbol we got originally is still valid */ + CHECK(tsRemapInitSym_v1_t1 == tsRemapInitSym_v1_t2); + } + + teardownConfigPathTest(factory2); + enablePluginDynamicReload(); + } + } + + GIVEN("two different versions v1 and v2 of same plugin in mixed mode - dynamic plugin reload ENABLED (negative test)") + { + WHEN("(1) loading v1, (2) overwriting with v2 and then (3) reloading by using the same plugin name, " + "(*) v1 and v2 DSOs modification time are different (changed)") + { + enablePluginDynamicReload(); + /* Simulate installing plugin plugin_v1.so (ver 1) as plugin.so and loading it at some point of time t1 */ + setupConfigPathTest(configName, buildPath_v1, uuid_t1, effectivePath_v1, runtimePath_v1, 1556825556); + GlobalPluginInfo global_plugin_v1; + CHECK(global_plugin_v1.loadDso(effectivePath_v1) == true); + global_plugin_v1.getSymbol("TSRemapInit", tsRemapInitSym_v1_t1, error); + + /* Simulate installing plugin plugin_v2.so (v1) as plugin.so and loading it at some point of time t2 */ + /* Note that during the installation plugin_v2.so (v2) is "barberically" overriding the existing plugin.so which was v1 */ + auto [plugin_v2, factory2] = + testSetupLoadPlugin(configName, buildPath_v2, uuid_t2, 1556825557, effectivePath_v2, runtimePath_v2); + + CHECK(effectivePath_v1 == effectivePath_v2); + /* since dynamic reload is enabled runtime paths would be different */ + CHECK(runtimePath_v1 != runtimePath_v2); + + THEN("expect both to be successfully loaded and used simultaneously") + { + validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v2); + + /* Make sure what we installed and loaded first was v1 and after the plugin reload we run v2 */ + CHECK(1 == global_plugin_v1.getPluginVersion()); + CHECK(2 == getPluginVersion(plugin_v2->_plugin)); + CHECK(global_plugin_v1.dlOpenHandle() != plugin_v2->_plugin.dlOpenHandle()); + + /* Make sure the symbols we get from the 2 loaded plugins don't yield the same callback function pointer */ + global_plugin_v1.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error); + plugin_v2->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v2_t2, error); + CHECK(nullptr != tsRemapInitSym_v1_t2); + CHECK(nullptr != tsRemapInitSym_v2_t2); + CHECK(tsRemapInitSym_v1_t2 != tsRemapInitSym_v2_t2); + + /* Make sure v1 callback functions addresses did not change for v1 after v2 was loaded */ + CHECK(tsRemapInitSym_v1_t1 == tsRemapInitSym_v1_t2); + } + + teardownConfigPathTest(factory2); + enablePluginDynamicReload(); + } + } +} + SCENARIO("notifying plugins of config reload", "[plugin][core]") { REQUIRE_FALSE(sandboxDir.empty()); + enablePluginDynamicReload(); /* use 2 copies of the same plugin to test */ fs::path configName1 = fs::path("plugin_testing_calls_1.so"); @@ -629,7 +1048,7 @@ SCENARIO("notifying plugins of config reload", "[plugin][core]") /* Simulate configuration with 1 factory and 1 plugin */ setupConfigPathTest(configName1, buildPath, uuid_t1, effectivePath1, runtimePath1, 1556825556); PluginFactoryUnitTest *factory1 = getFactory(uuid_t1); - RemapPluginInst *plugin1 = factory1->getRemapPlugin(configName1, 0, nullptr, error); + RemapPluginInst *plugin1 = factory1->getRemapPlugin(configName1, 0, nullptr, error, isPluginDynamicReloadEnabled()); /* check if loaded successfully */ validateSuccessfulConfigPathTest(plugin1, error, effectivePath1, runtimePath1); @@ -687,8 +1106,8 @@ SCENARIO("notifying plugins of config reload", "[plugin][core]") setupConfigPathTest(configName1, buildPath, uuid_t1, effectivePath1, runtimePath1, 1556825556); setupConfigPathTest(configName2, buildPath, uuid_t1, effectivePath2, runtimePath2, 1556825556, /* append */ true); PluginFactoryUnitTest *factory1 = getFactory(uuid_t1); - RemapPluginInst *plugin1 = factory1->getRemapPlugin(configName1, 0, nullptr, error); - RemapPluginInst *plugin2 = factory1->getRemapPlugin(configName2, 0, nullptr, error); + RemapPluginInst *plugin1 = factory1->getRemapPlugin(configName1, 0, nullptr, error, isPluginDynamicReloadEnabled()); + RemapPluginInst *plugin2 = factory1->getRemapPlugin(configName2, 0, nullptr, error, isPluginDynamicReloadEnabled()); /* check if loaded successfully */ validateSuccessfulConfigPathTest(plugin1, error, effectivePath1, runtimePath1); @@ -765,8 +1184,8 @@ SCENARIO("notifying plugins of config reload", "[plugin][core]") setupConfigPathTest(configName1, buildPath, uuid_t1, effectivePath1, runtimePath1, 1556825556); PluginFactoryUnitTest *factory1 = getFactory(uuid_t1); PluginFactoryUnitTest *factory2 = getFactory(uuid_t2); - RemapPluginInst *plugin1 = factory1->getRemapPlugin(configName1, 0, nullptr, error); - RemapPluginInst *plugin2 = factory2->getRemapPlugin(configName1, 0, nullptr, error); + RemapPluginInst *plugin1 = factory1->getRemapPlugin(configName1, 0, nullptr, error, isPluginDynamicReloadEnabled()); + RemapPluginInst *plugin2 = factory2->getRemapPlugin(configName1, 0, nullptr, error, isPluginDynamicReloadEnabled()); /* Prepare the debug objects */ PluginDebugObject *debugObject1 = getDebugObject(plugin1->_plugin); @@ -806,11 +1225,11 @@ SCENARIO("notifying plugins of config reload", "[plugin][core]") PluginFactoryUnitTest *factory2 = getFactory(uuid_t2); /* 2 plugins loaded by the 1st factory */ - RemapPluginInst *pluginInst1 = factory1->getRemapPlugin(configName1, 0, nullptr, error); - RemapPluginInst *pluginInst2 = factory1->getRemapPlugin(configName2, 0, nullptr, error); + RemapPluginInst *pluginInst1 = factory1->getRemapPlugin(configName1, 0, nullptr, error, isPluginDynamicReloadEnabled()); + RemapPluginInst *pluginInst2 = factory1->getRemapPlugin(configName2, 0, nullptr, error, isPluginDynamicReloadEnabled()); /* only 1 plugin loaded by the 2st factory */ - RemapPluginInst *pluginInst3 = factory2->getRemapPlugin(configName1, 0, nullptr, error); + RemapPluginInst *pluginInst3 = factory2->getRemapPlugin(configName1, 0, nullptr, error, isPluginDynamicReloadEnabled()); /* pluginInst1 and pluginInst3 should be using the same plugin DSO named configName1 * pluginInst2 should be using plugin DSO named configName 2*/ diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index 619f4f51dbc..0a8f0195009 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -2042,6 +2042,8 @@ main(int /* argc ATS_UNUSED */, const char **argv) // initialize logging (after event and net processor) Log::init(remote_management_flag ? 0 : Log::NO_REMOTE_MANAGEMENT); + (void)parsePluginConfig(); + // Init plugins as soon as logging is ready. (void)plugin_init(); // plugin.config