Skip to content

Conversation

@guybe7
Copy link
Collaborator

@guybe7 guybe7 commented Sep 15, 2021

Intro

The purpose is to allow having different flags/ACL categories for
subcommands (Example: CONFIG GET is ok-loading but CONFIG SET isn't)

We create a small command table for every command that has subcommands
and each subcommand has its own flags, etc. (same as a "regular" command)

This commit also unites the Redis and the Sentinel command tables

Affected commands

CONFIG
Used to have "admin ok-loading ok-stale no-script"
Changes:

  1. Dropped "ok-loading" in all except GET (this doesn't change behavior since there were checks in the code doing that)

XINFO
Used to have "read-only random"
Changes:

  1. Dropped "random" in all except CONSUMERS

XGROUP
Used to have "write use-memory"
Changes:

  1. Dropped "use-memory" in all except CREATE and CREATECONSUMER

COMMAND
No changes.

MEMORY
Used to have "random read-only"
Changes:

  1. Dropped "random" in PURGE and USAGE

ACL
Used to have "admin no-script ok-loading ok-stale"
Changes:

  1. Dropped "admin" in WHOAMI, GENPASS, and CAT

LATENCY
No changes.

MODULE
No changes.

SLOWLOG
Used to have "admin random ok-loading ok-stale"
Changes:

  1. Dropped "random" in RESET

OBJECT
Used to have "read-only random"
Changes:

  1. Dropped "random" in ENCODING and REFCOUNT

SCRIPT
Used to have "may-replicate no-script"
Changes:

  1. Dropped "may-replicate" in all except FLUSH and LOAD

CLIENT
Used to have "admin no-script random ok-loading ok-stale"
Changes:

  1. Dropped "random" in all except INFO and LIST
  2. Dropped "admin" in ID, TRACKING, TRACKINGINFO, CACHING, GETREDIR, INFO, SETNAME, GETNAME, and REPLY

STRALGO
No changes.

PUBSUB
No changes.

CLUSTER
Changes:

  1. Dropped "admin in countkeysinslots, getkeysinslot, info, nodes, keyslot, myid, and slots

SENTINEL
No changes.

(note that DEBUG also fits, but we decided not to convert it since it's for
debugging and anyway undocumented)

New sub-command

This commit adds another element to the per-command output of COMMAND,
describing the list of subcommands, if any (in the same structure as "regular" commands)
Also, it adds a new subcommand:

COMMAND LIST [FILTERBY (MODULE <module-name>|ACLCAT <cat>|PATTERN <pattern>)]

which returns a set of all commands (unless filters), but excluding subcommands.

Module API

A new module API, RM_CreateSubcommand, was added, in order to allow
module writer to define subcommands

ACL changes:

  1. Now, that each subcommand is actually a command, each has its own ACL id.
  2. The old mechanism of allowed_subcommands is redundant
    (blocking/allowing a subcommand is the same as blocking/allowing a regular command),
    but we had to keep it, to support the widespread usage of allowed_subcommands
    to block commands with certain args, that aren't subcommands (e.g. "-select +select|0").
  3. I have renamed allowed_subcommands to allowed_firstargs to emphasize the difference.
  4. Because subcommands are commands in ACL too, you can now use "-" to block subcommands
    (e.g. "+client -client|kill"), which wasn't possible in the past.
  5. It is also possible to use the allowed_firstargs mechanism with subcommand.
    For example: +config -config|set +config|set|loglevel will block all CONFIG SET except
    for setting the log level.
    NOTE: this capability was later removed in Improved handling of subcommands (don't allow ACL on first-arg of a sub-command) #10147
  6. All of the ACL changes above required some amount of refactoring.

Misc

  1. There are two approaches: Either each subcommand has its own function or all
    subcommands use the same function, determining what to do according to argv[0].
    For now, I took the former approaches only with CONFIG and COMMAND,
    while other commands use the latter approach (for smaller blamelog diff).
  2. Deleted memoryGetKeys: It is no longer needed because MEMORY USAGE now uses the "range" key spec.
  3. Bugfix: GETNAME was missing from CLIENT's help message.
  4. Sentinel and Redis now use the same table, with the same function pointer.
    Some commands have a different implementation in Sentinel, so we redirect
    them (these are ROLE, PUBLISH, and INFO).
  5. Command stats now show the stats per subcommand (e.g. instead of stats just
    for "config" you will have stats for "config|set", "config|get", etc.)
  6. It is now possible to use COMMAND directly on subcommands:
    COMMAND INFO CONFIG|GET (The pipeline syntax was inspired from ACL, and
    can be used in functions lookupCommandBySds and lookupCommandByCString)
  7. STRALGO is now a container command (has "help")

Breaking changes:

  1. Command stats now show the stats per subcommand (see (5) above)

TODO

  • need to update the acl topic in redis.io (both the categories and the sub-commands) - with some note about redis 7 and up

The purpose is to allow having different flags/ACL categories for
subcommands (Example: CONFIG GET is ok-loading but CONFIG SET isn't)

We create a small command table for every command that has subcommands
and each subcommand has its own flags, etc. (same as a "regular" command)

This commit also unites the Redis and the Sentinel command tables

This commit adds a new command COMMANDS in order to preserve
backward-compatibility of COMMAND and also in the intention
of deprecating it.
@guybe7
Copy link
Collaborator Author

guybe7 commented Sep 15, 2021

modules: maybe we want something like RM_CreateContainerCommand + RM_CreateSubcommand ?

@guybe7
Copy link
Collaborator Author

guybe7 commented Sep 15, 2021

@oranagra @madolson please go over all subcommands' flags: I've put some thought in some (CONFIG's subcommand) but in others, I just copied the flags from the parent command (CLUSTER)

@guybe7 guybe7 requested review from madolson and oranagra September 15, 2021 10:24
Copy link
Member

@oranagra oranagra left a comment

Choose a reason for hiding this comment

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

part one of the review.
reviewed everything except the command table itself.

@soloestoy
Copy link
Contributor

I didn't go deep into this PR, the subcommand idea is good, but a problem that came to my mind is about module, how to let module to register subcommands?

@guybe7
Copy link
Collaborator Author

guybe7 commented Sep 16, 2021

@soloestoy I guess we should introduce a new API RM_CreateSubcommand which is identical to RM_CreateCommand except that it gets const char* container_name (and maybe also drop the old [first,last,step] scheme, not sure)

@guybe7
Copy link
Collaborator Author

guybe7 commented Sep 16, 2021

TODO:

  1. module API
  2. COMMANDS LIST [FILTERBY (MODULE <module-name>|ACLCAT <cat>|GROUP <group>|PATTERN <pattern>)]
  3. different ACL command-id per subcommand. pay special attention to backward-compatibility.

@oranagra please ack

Copy link
Contributor

@madolson madolson left a comment

Choose a reason for hiding this comment

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

Some comments

@oranagra oranagra added release-notes indication that this issue needs to be mentioned in the release notes state:major-decision Requires core team consensus state:needs-doc-pr requires a PR to redis-doc repository labels Sep 19, 2021
@guybe7 guybe7 force-pushed the promote_subcommands branch from 27b21f9 to dcc19f6 Compare September 21, 2021 08:06
Copy link
Contributor

@madolson madolson left a comment

Choose a reason for hiding this comment

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

I think the ACL code is correct, but adding tests for various cases like -@all +config -config|get work would probably be good.

Copy link
Collaborator

@yossigo yossigo left a comment

Choose a reason for hiding this comment

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

LGTM, only skimmed through the code - but concept and API make sense and look great.

@oranagra oranagra merged commit 43e736f into redis:unstable Oct 20, 2021
oranagra pushed a commit that referenced this pull request Oct 24, 2021
Introduced via typo in #9504. 
Also adds a sanity test for coverage.
oranagra pushed a commit that referenced this pull request Oct 25, 2021
oranagra added a commit that referenced this pull request Oct 25, 2021
Improve code doc for allowed_firstargs (used to be allowed_commands before #9504.
I don't think the text in the code needs to refer to the history (it's not there just for backwards compatibility).
instead it should just describe what it does.
enjoy-binbin added a commit to enjoy-binbin/redis that referenced this pull request Jan 9, 2022
The following error commands will crash redis-server:
```
> get|
Error: Server closed the connection
> get|set
Error: Server closed the connection
> get|other
```

The reason is in redis#9504, we use `lookupCommandBySds` for find the
container command. And it split the command (argv[0]) with `|`.
If we input something like `get|other`, after the split, `get`
will become a valid command name, pass the `ERR unknown command`
check, and finally crash in `addReplySubcommandSyntaxError`

In this case we do not need to split the command name with `|`
and just look in the commands dict to find if `argv[0]` is a
container command.

So this commit introduce a new function call `isContainerCommandBySds`
that it will return true if a command name is a container command.

Also with the old code, there is a incorrect error message:
```
> config|get set
(error) ERR Unknown subcommand or wrong number of arguments for 'set'. Try CONFIG|GET HELP.
```

The crash was reported in redis#10070.
oranagra pushed a commit that referenced this pull request Jan 9, 2022
The following error commands will crash redis-server:
```
> get|
Error: Server closed the connection
> get|set
Error: Server closed the connection
> get|other
```

The reason is in #9504, we use `lookupCommandBySds` for find the
container command. And it split the command (argv[0]) with `|`.
If we input something like `get|other`, after the split, `get`
will become a valid command name, pass the `ERR unknown command`
check, and finally crash in `addReplySubcommandSyntaxError`

In this case we do not need to split the command name with `|`
and just look in the commands dict to find if `argv[0]` is a
container command.

So this commit introduce a new function call `isContainerCommandBySds`
that it will return true if a command name is a container command.

Also with the old code, there is a incorrect error message:
```
> config|get set
(error) ERR Unknown subcommand or wrong number of arguments for 'set'. Try CONFIG|GET HELP.
```

The crash was reported in #10070.
parent->subcommands_dict = dictCreate(&commandTableDictType);

subcommand->parent = parent; /* Assign the parent command */
sds fullname = getFullCommandName(subcommand);
Copy link
Contributor

Choose a reason for hiding this comment

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

can we just set the subcommand->name = fullname? (or add a fullname field....)
it seems to me now that the subcommand name are generally unusable, we must get the fullname to use it.
so i suppose if we can just set the name = fullname, when we need it, we can just use it...

like here, it looks like the subcommand won't get the full name (unfamiliar with modules)

redis/src/module.c

Lines 10198 to 10204 in 56a8020

/* Return the name of the command currently running */
const char *RM_GetCurrentCommandName(RedisModuleCtx *ctx) {
if (!ctx || !ctx->client || !ctx->client->cmd)
return NULL;
return (const char*)ctx->client->cmd->name;
}

Copy link
Member

@oranagra oranagra Jan 14, 2022

Choose a reason for hiding this comment

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

interesting..
in theory changing that (past 7.0) could be a breaking change.
but currently (before 7.0 is released), modules can't register sub-commands.

@enjoy-binbin did you conduct some search to see who's still using the simple cmd->name (on something that could be a sub-command) to find that one?
if that's the only one, i tend to agree we wanna make name the full name by default and drop all the complication of getFullCommandName

Copy link
Contributor

Choose a reason for hiding this comment

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

did you conduct some search to see who's still using the simple cmd->name (on something that could be a sub-command) to find that one?

actually no, i happen to see it (reviewing / studying some module codes.)

i also tend to use the full name by default, so that we don't have to deal with getFullCommandName.
i do some search, here are what i found:

  1. latencyAllCommandsFillCDF, show sub-commands INFO latencystats and LATENCY HISTOGRAM #10103

  2. afterErrorReply (we need to take care of it, it could be a subcommand i think):

const char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
                     "to its %s: '%.*s' after processing the command "
                     "'%s'", from, to, (int)len, s, cmdname);
  1. ACL CAT? maybe we need to also print the subcommand in future? (for now, it only works on parentcommand)

  2. COMMAND LIST? same as above, only print the parent command, but others one, like command docs config|get, can work on subcommand

Copy link
Member

@oranagra oranagra Jan 16, 2022

Choose a reason for hiding this comment

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

@guybe7 considering sub-commands are now first class citizens, i suppose we should list them in ACL CAT and COMMAND LIST, WDYT?

and IIRC from @enjoy-binbin findings, we might be better off just deleting getFullCommandName and put the full command name in cmd->name, which will also fix:

  • RM_GetCurrentCommandName module API
  • the CRITICAL log warning in afterErrorReply
  • replies in COMMAND LIST and ACL CAT once we change them to include sub-commands.

Copy link
Member

Choose a reason for hiding this comment

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

@enjoy-binbin Guy didn't respond yet, but i'm paranoid about forgetting this (discussion in a closed PR), and i think we must do that in 7.0 for completeness. wanna make a PR and we can continue the discussion there if needed?

Copy link
Collaborator Author

@guybe7 guybe7 Jan 17, 2022

Choose a reason for hiding this comment

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

@oranagra @enjoy-binbin I agree. we should always store the fullname, and eliminate all uses getFullCommandName, except for the initial assignment of the name.
we should rename redisCommand.name to something like tmp_cmdname so that the compiler will make sure we didn't miss anything. after that, we can call it name again. another option is to change it to fullname permanently in order to cause conflicts in open PRs such as #10103

Copy link
Contributor

Choose a reason for hiding this comment

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

i 'd like go the fullname option.
at the same time we should also care about subcommands_dict, now the key is the subcommand short name.
we can treat name as the short name(or rename it in some function arg name), fullname as the full name

Copy link
Collaborator Author

@guybe7 guybe7 Jan 17, 2022

Choose a reason for hiding this comment

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

i think that the command name is always the full name (also in the ubcommand_dict)
to clarify, the word "rewrite" from CONFIG REWRITE should never appear anywhere by itself, it should always be saved as "config|rewrite"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

suggestion: we need to make sure that all callers to commandAddSubcommand already pass the subcommand with the fullname

Copy link
Member

Choose a reason for hiding this comment

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

@enjoy-binbin maybe while you're working on it, you can also handle #10124 and see if we missed anything.

oranagra added a commit that referenced this pull request Jan 23, 2022
… stores fullname (#10127)

Summary of changes:
1. Rename `redisCommand->name` to `redisCommand->declared_name`, it is a
  const char * for native commands and SDS for module commands.
2. Store the [sub]command fullname in `redisCommand->fullname` (sds).
3. List subcommands in `ACL CAT`
4. List subcommands in `COMMAND LIST`
5. `moduleUnregisterCommands` now will also free the module subcommands.
6. RM_GetCurrentCommandName returns full command name

Other changes:
1. Add `addReplyErrorArity` and `addReplyErrorExpireTime`
2. Remove `getFullCommandName` function that now is useless.
3. Some cleanups about `fullname` since now it is SDS.
4. Delete `populateSingleCommand` function from server.h that is useless.
5. Added tests to cover this change.
6. Add some module unload tests and fix the leaks
7. Make error messages uniform, make sure they always contain the full command
  name and that it's quoted.
7. Fixes some typos

see the history in #9504, fixes #10124

Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: guybe7 <guy.benoish@redislabs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approval-needed Waiting for core team approval to be merged breaking-change This change can potentially break existing application release-notes indication that this issue needs to be mentioned in the release notes state:major-decision Requires core team consensus state:needs-doc-pr requires a PR to redis-doc repository

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

7 participants