Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

Checkconf (2nd attempt) #733

Merged
merged 24 commits into from
Jun 15, 2016
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cf3b3b1
add checkconf interface for plugins
petermax2 May 16, 2016
5858ed6
add checkconf example to template plugin
petermax2 May 17, 2016
7fd6416
mark optional functions in template plugin
petermax2 May 17, 2016
93e607c
documentation of checkconf
petermax2 May 17, 2016
034d202
fix compile warning
petermax2 May 17, 2016
c91f567
establish return value convention for checkconf
petermax2 May 18, 2016
c18cb8e
documentation of checkconf
petermax2 May 18, 2016
6ad66dd
Merge branch 'master' into checkconf2
petermax2 May 18, 2016
78cdd34
remove odd exception handling in plugindatabase.cpp
petermax2 May 20, 2016
dff7a6e
prettify code in plugindatabase.cpp
petermax2 May 20, 2016
5e146af
improve checkconf documentation
petermax2 May 20, 2016
0caed1e
add checkconf example to plugins.md tutorial
petermax2 May 20, 2016
0639d04
add unit tests for checkconf (WARNING: contains bug)
petermax2 May 20, 2016
6de7280
replace exception with 0-return in ModulesPluginDatabase::getSymbol
petermax2 May 20, 2016
7bb49be
split mockup functions in MockPluginDatabase
petermax2 May 20, 2016
274a395
simplify checkconf test cases
petermax2 May 21, 2016
d15a8d9
add checkconf test case
petermax2 May 21, 2016
7bf749b
add PluginSpec::setConfig()
petermax2 May 21, 2016
e9ccac6
reformat source
petermax2 May 21, 2016
ac8c148
simplify checkconf mockup in testtool_backendbuilder.cpp
petermax2 May 21, 2016
742cf1a
document the meaning of MountBackendBuilder::mountConf
petermax2 May 21, 2016
5d92d69
move backendConfig up to BackendBuilder
petermax2 May 22, 2016
a904008
pass backend config to checkconf
petermax2 May 22, 2016
aed7053
fix memory leak in backendbuilder.cpp
petermax2 May 22, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions doc/tutorials/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Additionally, there is one more function called
[ELEKTRA_PLUGIN_EXPORT](http://doc.libelektra.org/api/current/html/group__plugin.html#gabe78724d2d477eef39997fd9b85bff16),
where once again `Plugin` should be replaced with the name of the plug-in, this time in lower-case. So for my line plugin this function would be
`ELEKTRA_PLUGIN_EXPORT(line)`.
The developer may define elektraPluginCheckConf() if configuration validation at mount time is desired.

The KDB relies on the first five functions for interacting with configuration files stored in the key database.
Calls for kdbGet() and kdbClose() will call the functions elektraPluginGet() and elektraPluginClose() respectively for the
Expand Down Expand Up @@ -322,6 +323,38 @@ The `elektraPluginOpen` and `elektraPluginClose` functions are not commonly used
reviewing. `elektraPluginOpen` function runs before `elektraPluginGet` and is useful to do initialization if necessary for the plug-in. On the other
hand `elektraPluginClose` is run after other functions of the plug-in and can be useful for freeing up resources.

### elektraPluginCheckConf ###

The `elektraPluginCheckConf` function may be used for validation of the plugin configuration during mount time. The signature of the function is:

int elektraLineCheckConfig (Key * errorKey, KeySet * conf)

The configuration of the plugin is provided as `conf`. The function may report an error or warnings using the `errorKey` and the return value.

The following convention was established for the return value of `elektraPluginCheckConf`:

- 0: The configuration was OK and has not been changed
- 1: The configuration has been changed and now it is OK
- -1: The configuration was not OK and could not be fixed. An error has to be set to errorKey.

The following example demonstrates how to limit the length of the values within the plugin configuration to 3 charaters.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markus2330 @Namoshek @iandonnelly is this a viable example?


int elektraLineCheckConfig (Key * errorKey, KeySet * conf)
{
Key * cur;
ksRewind (conf);
while ((cur = ksNext (conf)) != 0)
{
const char * value = keyString (cur);
if (strlen (value) > 3)
{
ELEKTRA_SET_ERRORF (ELEKTRA_ERROR_VALUE_LENGTH, errorKey, "value %s is more than 3 characters long", value);
return -1; // The configuration was not OK and could not be fixed
}
}
return 0; // The configuration was OK and has not been changed
}

### ELEKTRA_PLUGIN_EXPORT ###

The last function, one that is always needed in a plug-in, is `ELEKTRA_PLUGIN_EXPORT`. This functions is responsible for letting Elektra know that
Expand Down
15 changes: 15 additions & 0 deletions src/libs/tools/include/plugindatabase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ namespace tools
*/
class PluginDatabase
{
protected:
typedef void (*func_t) ();

public:
/**
* @brief list all plugins
Expand Down Expand Up @@ -68,6 +71,16 @@ class PluginDatabase
*/
virtual std::string lookupInfo (PluginSpec const & whichplugin, std::string const & which) const = 0;

/**
* @brief get exported plugin symbol
*
* @param whichplugin from which plugin?
* @param which which symbol would you like to look up?
*
* @return the function pointer to the exported symbol
*/
virtual func_t getSymbol (PluginSpec const & whichplugin, std::string const & which) const = 0;

/**
* @brief lookup which plugin handles meta data
*
Expand Down Expand Up @@ -118,6 +131,7 @@ class ModulesPluginDatabase : public PluginDatabase
std::vector<std::string> listAllPlugins () const;
PluginDatabase::Status status (PluginSpec const & whichplugin) const;
std::string lookupInfo (PluginSpec const & spec, std::string const & which) const;
func_t getSymbol (PluginSpec const & whichplugin, std::string const & which) const;
PluginSpec lookupMetadata (std::string const & which) const;
PluginSpec lookupProvides (std::string const & provides) const;
};
Expand All @@ -135,6 +149,7 @@ class MockPluginDatabase : public ModulesPluginDatabase
std::vector<std::string> listAllPlugins () const;
PluginDatabase::Status status (PluginSpec const & whichplugin) const;
std::string lookupInfo (PluginSpec const & spec, std::string const & which) const;
func_t getSymbol (PluginSpec const & whichplugin, std::string const & which) const;
};
}
}
Expand Down
29 changes: 29 additions & 0 deletions src/libs/tools/include/toolexcept.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,35 @@ struct PluginAlreadyInserted : public PluginCheckException
std::string m_str;
};

struct PluginConfigInvalid : public PluginCheckException
{
explicit PluginConfigInvalid (Key key) : m_key (key), m_str ()
{
}

explicit PluginConfigInvalid (std::string const & message) : m_str (message)
{
}

virtual const char * what () const throw () override
{
if (m_str.empty ())
{
std::stringstream ss;
ss << "The provided plugin configuration is not valid!\n";
ss << "Errors/Warnings during the check were:\n";
printError (ss, m_key);
printWarnings (ss, m_key);
m_str = ss.str ();
}
return m_str.c_str ();
}

private:
Key m_key;
mutable std::string m_str;
};

struct BadPluginName : public PluginCheckException
{
explicit BadPluginName (std::string name)
Expand Down
29 changes: 29 additions & 0 deletions src/libs/tools/src/backendbuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,12 +384,18 @@ void BackendBuilder::recommendPlugin (std::string name)
* @pre Needs to be a unique new name (use refname if you want to add the same module multiple times)
*
* Will automatically resolve virtual plugins to actual plugins.
*
* Also calls the checkconf function if provided by the plugin. The checkconf function has the
* following signature: int checkconf (Key * errorKey, KeySet * config) and allows a plugin to
* verify its configuration at mount time.
*
* @see resolveNeeds()
* @param plugin
*/
void BackendBuilder::addPlugin (PluginSpec const & plugin)
{
typedef int (*checkConfPtr) (ckdb::Key *, ckdb::KeySet *);

for (auto & p : toAdd)
{
if (p.getFullName () == plugin.getFullName ())
Expand All @@ -409,6 +415,29 @@ void BackendBuilder::addPlugin (PluginSpec const & plugin)
newPlugin.appendConfig (provides.getConfig ());
}

// call plugin's checkconf function (if provided)
// this enables a plugin to verify its configuration at mount time
checkConfPtr checkConfFunction = nullptr;
try
{
checkConfFunction = reinterpret_cast<checkConfPtr> (pluginDatabase->getSymbol (newPlugin, "checkconf"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can return 0 here instead of exception?

}
catch (...)
{
// the checkconf operation is optional, so no worries if it was not found
}

if (checkConfFunction != nullptr)
{
ckdb::Key * errorKey = ckdb::keyNew (0);
int checkResult = checkConfFunction (errorKey, newPlugin.getConfig ().getKeySet ());
if (checkResult == -1)
{
throw PluginConfigInvalid (errorKey);
}
ckdb::keyDel (errorKey);
}

toAdd.push_back (newPlugin);
sort ();
}
Expand Down
24 changes: 14 additions & 10 deletions src/libs/tools/src/plugindatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,16 @@ PluginDatabase::Status ModulesPluginDatabase::status (PluginSpec const & spec) c

std::string ModulesPluginDatabase::lookupInfo (PluginSpec const & spec, std::string const & which) const
{
PluginPtr plugin;
try
{
plugin = impl->modules.load (spec.getName (), spec.getConfig ());
}
catch (...)
{
throw;
}

PluginPtr plugin = impl->modules.load (spec.getName (), spec.getConfig ());
return plugin->lookupInfo (which);
}

PluginDatabase::func_t ModulesPluginDatabase::getSymbol (PluginSpec const & spec, std::string const & which) const
{
PluginPtr plugin = impl->modules.load (spec.getName (), spec.getConfig ());
return plugin->getSymbol (which);
}

PluginSpec ModulesPluginDatabase::lookupMetadata (std::string const & which) const
{
std::vector<std::string> allPlugins = listAllPlugins ();
Expand Down Expand Up @@ -367,5 +364,12 @@ std::string MockPluginDatabase::lookupInfo (PluginSpec const & spec, std::string

return "";
}

PluginDatabase::func_t MockPluginDatabase::getSymbol (PluginSpec const & spec ELEKTRA_UNUSED,
std::string const & which ELEKTRA_UNUSED) const
{
// TODO implement mockup
return NULL;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it looks pretty well!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment there is no use for the mocked method. @markus2330 any advice for a good implementation or should I just leave it as it is?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For unit tests you should return some function as done for other mocks with data. Then you can test if the mounting process fails/succeeds as desired with different checkplugin configs. There should be at least 3 unit tests: with the 3 return values checkconf has.

}
}
14 changes: 14 additions & 0 deletions src/plugins/doc/doc.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ int elektraDocGet (Plugin * plugin ELEKTRA_UNUSED, KeySet * returned, Key * pare
keyNew ("system/elektra/modules/doc/exports/get", KEY_FUNC, elektraDocGet, KEY_END),
keyNew ("system/elektra/modules/doc/exports/set", KEY_FUNC, elektraDocSet, KEY_END),
keyNew ("system/elektra/modules/doc/exports/error", KEY_FUNC, elektraDocError, KEY_END),
keyNew ("system/elektra/modules/doc/exports/checkconf", KEY_FUNC, elektraDocCheckConf, KEY_END),
#include ELEKTRA_README (doc)
keyNew ("system/elektra/modules/doc/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
ksAppend (returned, contract);
Expand Down Expand Up @@ -183,6 +184,19 @@ static void saveToDisc (Key * k ELEKTRA_UNUSED)
{
}

//![validate configuration]
int elektraDocCheckConf (Key * errorKey ELEKTRA_UNUSED, KeySet * conf ELEKTRA_UNUSED)
{
/* validate plugin configuration */

// the return codes have the following meaning:
// 0: The configuration was OK and has not been changed
// 1: The configuration has been changed and now it is OK
// -1: The configuration was not OK and could not be fixed. An error has to be set to errorKey.
return 0;
}
//![validate configuration]

//![set full]
static void usercode (KDB * handle, KeySet * keyset, Key * key)
{
Expand Down
22 changes: 22 additions & 0 deletions src/plugins/doc/doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,28 @@ int elektraDocSet (Plugin * handle, KeySet * returned, Key * parentKey);
*/
int elektraDocError (Plugin * handle, KeySet * returned, Key * parentKey);

/**
* @brief Validate plugin configuration at mount time.
*
* During the mount phase the BackendBuilder calls this method,
* if it is provided by the plugin.
*
* In this method the plugin configuration can be checked for validity or
* integrity. Missing items can be added to complete the configuration.
*
* @param errorKey is used to propagate error messages to the caller
* @param conf contains the plugin configuration to be validated
*
* @retval 1 on success: the configuration was OK and has not been changed.
* @retval 0 on success: the configuration has been changed and now it is OK.
* @retval -1 on failure: the configuration was not OK and could not be fixed.
* Set an error using #ELEKTRA_SET_ERROR to inform the user what went wrong.
* Additionally you can add any number of warnings with
* #ELEKTRA_ADD_WARNING.
*
* @ingroup plugin
*/
int elektraDocCheckConf (Key * errorKey, KeySet * conf);

/**
* @}
Expand Down
17 changes: 17 additions & 0 deletions src/plugins/template/template.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
int elektraTemplateOpen (Plugin * handle ELEKTRA_UNUSED, Key * errorKey ELEKTRA_UNUSED)
{
// plugin initialization logic
// this function is optional

return 1; // success
}

int elektraTemplateClose (Plugin * handle ELEKTRA_UNUSED, Key * errorKey ELEKTRA_UNUSED)
{
// free all plugin resources and shut it down
// this function is optional

return 1; // success
}
Expand All @@ -38,6 +40,7 @@ int elektraTemplateGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTR
keyNew ("system/elektra/modules/template/exports/get", KEY_FUNC, elektraTemplateGet, KEY_END),
keyNew ("system/elektra/modules/template/exports/set", KEY_FUNC, elektraTemplateSet, KEY_END),
keyNew ("system/elektra/modules/template/exports/error", KEY_FUNC, elektraTemplateError, KEY_END),
keyNew ("system/elektra/modules/template/exports/checkconf", KEY_FUNC, elektraTemplateCheckConfig, KEY_END),
#include ELEKTRA_README (template)
keyNew ("system/elektra/modules/template/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
ksAppend (returned, contract);
Expand All @@ -53,17 +56,31 @@ int elektraTemplateGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTR
int elektraTemplateSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
{
// get all keys
// this function is optional

return 1; // success
}

int elektraTemplateError (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
{
// set all keys
// this function is optional

return 1; // success
}

int elektraTemplateCheckConfig (Key * errorKey ELEKTRA_UNUSED, KeySet * conf ELEKTRA_UNUSED)
{
// validate plugin configuration
// this function is optional

// the return codes have the following meaning:
// 0: The configuration was OK and has not been changed
// 1: The configuration has been changed and now it is OK
// -1: The configuration was not OK and could not be fixed. An error has to be set to errorKey.
return 0;
}

Plugin * ELEKTRA_PLUGIN_EXPORT (template)
{
// clang-format off
Expand Down
1 change: 1 addition & 0 deletions src/plugins/template/template.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ int elektraTemplateClose (Plugin * handle, Key * errorKey);
int elektraTemplateGet (Plugin * handle, KeySet * ks, Key * parentKey);
int elektraTemplateSet (Plugin * handle, KeySet * ks, Key * parentKey);
int elektraTemplateError (Plugin * handle, KeySet * ks, Key * parentKey);
int elektraTemplateCheckConfig (Key * errorKey, KeySet * conf);

Plugin * ELEKTRA_PLUGIN_EXPORT (template);

Expand Down