Skip to content

sys: Add RIOT Registry implementation#10799

Closed
leandrolanzieri wants to merge 1 commit intoRIOT-OS:masterfrom
leandrolanzieri:riot_registry
Closed

sys: Add RIOT Registry implementation#10799
leandrolanzieri wants to merge 1 commit intoRIOT-OS:masterfrom
leandrolanzieri:riot_registry

Conversation

@leandrolanzieri
Copy link
Copy Markdown
Contributor

@leandrolanzieri leandrolanzieri commented Jan 17, 2019

Contribution description

This adds the implementation of the RIOT Registry as discussed in #10622. This module is used for interacting with persistent key-value configurations, and is part of the ongoing effort to implement a Runtime Configuration System for RIOT nodes.

The RIOT Registry allows to register configuration groups via handlers, and non-volatile devices via storage facilities, and access the configuration parameters via its API.

Currently there are 3 storage facilities implemented, and more can be added as needed:

Testing procedure

There are unit tests for testing the RIOT Registry API (tests/unittests/tests-registry), and a test application that also serves as example or starting point for usage (tests/registry).

Issues/PRs references

EEPROM storage: #10910
File storage: #10911
Fixes #5773

@leandrolanzieri leandrolanzieri added State: WIP State: The PR is still work-in-progress and its code is not in its final presentable form yet State: waiting for other PR State: The PR requires another PR to be merged first TF: Config Marks issues and PRs related to the work of the Configuration Task Force labels Jan 17, 2019
@jia200x jia200x changed the title sys: Add RIOT Registry implementation [WIP] sys: Add RIOT Registry implementation Jan 17, 2019
@@ -0,0 +1,24 @@
#ifndef REGISTRY_STORE_DUMMY_H
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would put these dummy storage only as a test, not under sys

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

@danpetry danpetry removed the State: waiting for other PR State: The PR requires another PR to be merged first label Jan 24, 2019
Copy link
Copy Markdown
Contributor

@danpetry danpetry left a comment

Choose a reason for hiding this comment

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

Here's a review of the high level aspects.

  1. The testing philosophy needs some attention IMO. Specifically:
  • The application that is currently in the tests/ directory combines several modules that are individually testable and also requires changes to the makefile to run each test, which doesn’t make it super easy to use or CI-ready. It could make a good example/ application instead, what do you think? To have this would also be a good “advertisement” for the registry
  • Please explicitly and individually write tests for all API calls. That is for the registry, registry conversion module, and SFs. (You could have all tests be unit tests and mocking the interfaces using stubs like the dummy storage facility)
  • edge cases and things that could potentially break things (handing over null pointers etc) aren’t tested
  • The SFs aren’t tested extensibly – e.g. how do people implement tests for their SF when they add one? I’d suggest a separate test for each.
  1. Please make sure the tests, once you’ve got thorough coverage, compile and run successfully, with all tests passing, on all boards that aren’t blacklisted or that throw warnings due to unavailable features. Use FEATURES_REQUIRED += as appropriate as well as blacklisting boards that don’t build the test applications in Murdock

  2. folder structure/placement of stuff

  • Suggestion: registry_store stuff could go into registry.c to keep it all together? Or the file into the base folder maybe?
  • would all SFs and RHs be implemented in sys/registry? Or would they be in with their module? IMO whatever we do for SFs should be done for RHs to keep it consistent.
  1. Should we stipulate naming conventions for SF and RH APIs, or just set a precedent through these initial submissions? (Open question.)

  2. Would you consider separating out the storage facility implementations into separate PRs? This would make reviewing the whole thing more manageable.

  3. Helpers haven’t been implemented in the configuration manager (shell) in the test application to modify access control settings and enable and disable communication interfaces? The architecture doc says this MUST be done… which is wrong, the RDM or the tests/ application?

  4. Please bring the submission up to a final quality (i.e. not WIP) before further review of aspects 2-5

  5. Please rebase following the merge of PRs 10796 and 10802

@leandrolanzieri leandrolanzieri force-pushed the riot_registry branch 3 times, most recently from 4189131 to ce6f602 Compare January 31, 2019 09:34
@leandrolanzieri leandrolanzieri changed the title [WIP] sys: Add RIOT Registry implementation sys: Add RIOT Registry implementation Jan 31, 2019
@leandrolanzieri leandrolanzieri removed the State: WIP State: The PR is still work-in-progress and its code is not in its final presentable form yet label Jan 31, 2019
@leandrolanzieri
Copy link
Copy Markdown
Contributor Author

Hi @danpetry, thanks for the high level review, here are some comments:

Here's a review of the high level aspects.

  1. The testing philosophy needs some attention IMO. Specifically:
  • The application that is currently in the tests/ directory combines several modules that are individually testable and also requires changes to the makefile to run each test, which doesn’t make it super easy to use or CI-ready. It could make a good example/ application instead, what do you think? To have this would also be a good “advertisement” for the registry

Are you suggesting to add a test app per storage facility? I think that may be overkill, the only thing to change on the test app is the Makefile to select the wanted storage (the file storage adds some other parameters for configuring the file system, but that is independent of the registry). I think an example would be worth when other modules of the Runtime Configuration System are merged (e.g. the configuration managers).

  • Please explicitly and individually write tests for all API calls. That is for the registry, registry conversion module, and SFs. (You could have all tests be unit tests and mocking the interfaces using stubs like the dummy storage facility)

Added unit tests for all the registry API (also removed some that I figured should not be part of the public API)

  • edge cases and things that could potentially break things (handing over null pointers etc) aren’t tested

Check the updated code and please point out the edge cases so I can add them to the unit tests.

  • The SFs aren’t tested extensibly – e.g. how do people implement tests for their SF when they add one? I’d suggest a separate test for each.

They can be added to the test app. If it is really needed separate test can be implemented for that particular storage.

  1. Please make sure the tests, once you’ve got thorough coverage, compile and run successfully, with all tests passing, on all boards that aren’t blacklisted or that throw warnings due to unavailable features. Use FEATURES_REQUIRED += as appropriate as well as blacklisting boards that don’t build the test applications in Murdock

Fixed the problems with the file storage (#10911).

  1. folder structure/placement of stuff
  • Suggestion: registry_store stuff could go into registry.c to keep it all together? Or the file into the base folder maybe?

I found a bit cleaner to separate the conversions and the store functions from the main functionalities. Now all of them are in the base registry folder.

  • would all SFs and RHs be implemented in sys/registry? Or would they be in with their module? IMO whatever we do for SFs should be done for RHs to keep it consistent.

SFs are placed under sys/registry/store. The handlers are implemented in apps or other modules, so they should not be placed in the registry folder.

  1. Would you consider separating out the storage facility implementations into separate PRs? This would make reviewing the whole thing more manageable.

Good idea, done.

  1. Helpers haven’t been implemented in the configuration manager (shell) in the test application to modify access control settings and enable and disable communication interfaces? The architecture doc says this MUST be done… which is wrong, the RDM or the tests/ application?

The shell in this test application is not the suggested CLI (though pretty similar) from the RDM. The RDM has been updated to indicate that the configuration managers MAY implement this access control among other functionalities. Thanks for pointing that out.

  1. Please bring the submission up to a final quality (i.e. not WIP) before further review of aspects 2-5

Cleaned up and removed the WIP state now.

  1. Please rebase following the merge of PRs 10796 and 10802

Done!

@danpetry
Copy link
Copy Markdown
Contributor

danpetry commented Feb 4, 2019

Hi Leandro, sorry for the delay

Are you suggesting to add a test app per storage facility? I think that may be overkill, the only thing to change on the test app is the Makefile to select the wanted storage

Are there any other tests where the makefile needs to be changed or there are #defines to change tests? If so, then I'm ok with this. (I saw the unittests btw - looks good at first glance, thank you, and all the tests build correctly for the platforms I tried)

an example would be worth when other modules of the Runtime Configuration System are merged

sounds sensible

Check the updated code and please point out the edge cases

I'll have a closer look while doing the next round of review, maybe write some extra tests as a PR

SFs are placed under sys/registry/store. The handlers are implemented in apps or other modules, so they should not be placed in the registry folder.

ok, seems sensible, doesn't seem like it's feasible to make it symmetric

All other points on your post I would say agreed/fine/etc for.

@danpetry
Copy link
Copy Markdown
Contributor

danpetry commented Feb 4, 2019

I will do a review of the other aspects asap.

@danpetry danpetry added the Reviewed: 1-fundamentals The fundamentals of the PR were reviewed according to the maintainer guidelines label Feb 4, 2019
@leandrolanzieri
Copy link
Copy Markdown
Contributor Author

@kaspar030 any chance you could also take a look?

Copy link
Copy Markdown
Contributor

@danpetry danpetry left a comment

Choose a reason for hiding this comment

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

As discussed offline, these are the comments for the code review so far. More to come

Comment thread sys/include/registry/registry.h


/**
* @brief Prototype of a callback function for the load action of a store
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

for clarity: s/store/storage facility

typedef void (*load_cb_t)(char *name, char *val, void *cb_arg);

/**
* @brief Descriptor used to check duplications in store facilities
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

s/store facility/storage facility, here and below, i.e. keep the naming consistent across all documentation

*/
typedef struct {
clist_node_t node; /**< linked list node */
const struct registry_store_itf *itf; /**< interface for the facility */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you explain, why didn't you put the linked list node inside the interface struct like with the registry handlers? (also why is a *name not required?)

* @param[in] cb_arg Argument passed to @p cb function
* @return 0 on success, non-zero on failure
*/
int (*load)(registry_store_t *store, load_cb_t cb, void *cb_arg);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just wondering, why is there a registry_store_t* as an argument? Isn't all the code in the functions specific to a certain storage facility anyway, so you wouldn't have to pass a storage facility through the interface? Is this to add another potential abstraction layer, e.g. in the case of file storage where the underlying physical storage might be different but the "storage facility", i.e. the interface is the same?

Comment thread tests/registry/main.c
void registry_register(registry_handler_t *handler);

/**
* @brief Registers a new storage as a source of configurations. Multiple
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

s/storage/storage facility would be clearer I think (same below)
(same idea about standardizing the language so "storage facility" is always used for a storage facility)

int argc, char **argv, void *context);

void *context; /**< Optional context used by the handlers */
} registry_handler_t;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

After reading the code a bit I found that the name "registry handler" wasn't that intuitive. "Storage facility" clearly indicates what it means, but since "registry" is the whole thing being implemented, "registry handler" is a bit too generic. Something like just "config group" would be clearer imo. What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this should be discussed in the corresponding RDM: #10622

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

moved to there

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I've unresolved tho if that's ok just to remind us that it needs to be decided before merging this implementation

* @brief Storage facility interface.
* All store facilities should, at least, implement the load and save actions.
*/
typedef struct registry_store_itf {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the doxygen, it shows two typedefs (this and typedef void(* | load_cb_t) (char *name, char *val, void *cb_arg)) when there are actually a bunch of typedefs.the rest are in the data structures section. do you know why this is and whether it's necessary/possible to categorise them a bit more cleanly?

Comment thread tests/registry/main.c
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA};

char *get_handler(int argc, char **argv, char *val, int val_len_max, void *ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could move the handlers to another file called handlers.c so that people can use it as a reference to implement their own handler?

Copy link
Copy Markdown
Contributor

@danpetry danpetry left a comment

Choose a reason for hiding this comment

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

Here are some more. Let me know if posting the review bits at a time is a problem


/**
* @brief Convenience function to parse a configuration parameter value of
* `bytes` type from a string.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please point out that the encoding is base64, here and in the str_from_bytes function

*
* @param[in] argc Number of elements in @p argv
* @param[in] argv Parsed string representing the configuration parameter.
* @param[out] val Buffer containing the string of the new value
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wouldn't the set function need to know the size of the output buffer though, so it doesn't overflow?

int argc, char **argv, void *context);

void *context; /**< Optional context used by the handlers */
} registry_handler_t;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

moved to there

int argc, char **argv, void *context);

void *context; /**< Optional context used by the handlers */
} registry_handler_t;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I've unresolved tho if that's ok just to remind us that it needs to be decided before merging this implementation

Comment thread tests/registry/main.c
(void)ctx;

if (argc) {
if (!strcmp("opt1", argv[0])) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about an implementation of these config params which is more extensible? i.e., so you don't have to manually add each new param in all handlers. I'm thinking an array of structs, where the structs contain pointers to the encoding/decoding functions, the name, the value, the REGISTRY_TYPE, and anything else which might be needed. Then you could just loop through the array in each of the handlers. maybe with a switch-case statement inside the loop if required
Seems like this test is something like the reference implementation - people will likely go here to see how to implement their own handler

Comment thread sys/registry/registry.c
registry_handler_t *hndlr = container_of(current, registry_handler_t, node);
if (hndlr->hndlr_commit) {
_res = hndlr->hndlr_commit(hndlr->context);
if (!*(int *)res) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So does this mean that registry_commit stops if a commit handler returns a non-zero value and returns that error value? Could this be documented in the API documentation?

Comment thread sys/registry/registry.c
return hndlr->hndlr_commit(hndlr->context);
}
else {
return 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If there's no commit handler defined, I think it should return something more informative than "success", IMO

Comment thread sys/registry/registry.c
}
if (hndlr->hndlr_export) {
return hndlr->hndlr_export(export_func, name_argc - 1,
&name_argv[1], hndlr->context);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO name_argv + 1 is more readable but I guess this is a matter of style

Comment thread sys/registry/registry.c
&name_argv[1], hndlr->context);
}
else {
return 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as above, would be good to return something other than "success" if no handler present

Comment thread sys/registry/registry.c
}
else {
DEBUG("[registry export] exporting all\n");
clist_node_t *node = registry_handlers.next;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess you do this because the registry_handlers is a node which doesn't represent any registry handler at all?
If that's the case, the do loop below will call container_of with the registry_handlers node at some point I think, is that right? Would that lead to an error?

Copy link
Copy Markdown
Contributor

@danpetry danpetry left a comment

Choose a reason for hiding this comment

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

Some more comments, on registry_store.c specifically


void registry_store_init(void)
{
load_srcs.next = NULL;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

save_dst = NULL as well?

return -1;
}

if (save_dst->itf->save_start) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

just a check on save_dst != NULL? this would catch both if checks in this function. Also, if save_dst doesn't exist, it'll already have tried to dereference the pointer just to perform this check afaiu

int res = 0;
int res2;

if (!node) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With the null pointer checks, it would be nicer IMO to use a consistent syntax between if checks and assert checks, for code beautification reasons, but this is fairly marginal personal style reasons so nonblocking

return -ENOENT;
}

dup.name = (char *)name;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's my understanding that casting away constness is evil and should be avoided, because you've told the users of your API that an argument is const but in fact you can now modify it internally, which could lead to undefined behaviour from the point of view of the user.
Suggest making *name and *val in registry_dup_check_arg_t both const. Along with both the arguments to this function. If they won't need to be modified in any conceivable use cases that is

return 0;
}

static void _registry_dup_check_cb(char *name, char *val, void *cb_arg)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the logic below AFAIU is as follows:

  • Names not identical => is_dup = 0
  • Names identical and neither val or dup_arg->val present => is_dup = 1
  • Names identical and both val and dup_arg->val present and identical => is_dup = 1
  • Names identical and only one or other of val and dup_arg->val present => is_dup = 0

So, you can compare either just the name, or the name and the value, according to your choosing
This seems to give too much flexibility for an internal function; you're the only one who's using it. So you could just keep the check on both val and name, to save code size. And reduce some of the other checks too as far as possible and just make sure in the calling function that the arguments you're passing aren't causing any errors.

( I just remembered that elsewhere I suggested removing the duplication check. The above comments would apply only if removing the duplication check and silently copying over is considered a bad idea :) )


int registry_save_one(const char *name, char *value)
{
registry_store_t *dst = save_dst;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As far as I can tell you don't need this, you can just access save_dst

res = res2;
}
}
} while (node != registry_handlers.next);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So this will return the error code of the first failing hndlr_export function but continue exporting from the other handlers. This should be in the API documentation I would say

@jcarrano
Copy link
Copy Markdown
Contributor

@leandrolanzieri Can we split the registry itself into its own PR. This way we can comment on the registry only (and it will be more reviewable) and also we can dump the conversations we had in person.

@leandrolanzieri
Copy link
Copy Markdown
Contributor Author

@leandrolanzieri Can we split the registry itself into its own PR. This way we can comment on the registry only (and it will be more reviewable) and also we can dump the conversations we had in person.

Yes, I was planning on opening a new PR, only with the registry, with the changes we discussed on the configuration values and keys. I will keep it to the minimum so it's easier to review and discuss.

@stale
Copy link
Copy Markdown

stale Bot commented Feb 11, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you want me to ignore this issue, please mark it with the "State: don't stale" label. Thank you for your contributions.

@stale stale Bot added the State: stale State: The issue / PR has no activity for >185 days label Feb 11, 2020
@stale stale Bot closed this Mar 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Reviewed: 1-fundamentals The fundamentals of the PR were reviewed according to the maintainer guidelines State: stale State: The issue / PR has no activity for >185 days TF: Config Marks issues and PRs related to the work of the Configuration Task Force

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature request: persistent system config

4 participants