MINOR: map/acl: make "add map/acl" support an optional version number

By passing a version number to "add map/acl", it becomes possible to
atomically replace maps and ACLs. The principle is that a new version
number is first retrieved by calling"prepare map/acl", and this version
number is used with "add map" and "add acl". Newly added entries then
remain invisible to the matching mechanism but are visible in "show
map/acl" when the version number is specified, or may be cleard with
"clear map/acl". Finally when the insertion is complete, a
"commit map/acl" command must be issued, and the version is atomically
updated so that there is no intermediate state with incomplete entries.
This commit is contained in:
Willy Tarreau 2021-04-30 15:23:36 +02:00
parent 7a562ca809
commit bb51c44d64
2 changed files with 50 additions and 15 deletions

View File

@ -1397,22 +1397,36 @@ abort ssl cert <filename>
See also "set ssl cert" and "commit ssl cert". See also "set ssl cert" and "commit ssl cert".
add acl <acl> <pattern> add acl [@<ver>] <acl> <pattern>
Add an entry into the acl <acl>. <acl> is the #<id> or the <file> returned by Add an entry into the acl <acl>. <acl> is the #<id> or the <file> returned by
"show acl". This command does not verify if the entry already exists. This "show acl". This command does not verify if the entry already exists. Entries
command cannot be used if the reference <acl> is a file also used with a map. are added to the current version of the ACL, unless a specific version is
In this case, you must use the command "add map" in place of "add acl". specified with "@<ver>". This version number must have preliminary been
allocated by "prepare acl", and it will be comprised between the versions
reported in "curr_ver" and "next_ver" on the output of "show acl". Entries
added with a specific version number will not match until a "commit acl"
operation is performed on them. They may however be consulted using the
"show acl @<ver>" command, and cleared using a "clear acl @<ver>" command.
This command cannot be used if the reference <acl> is a file also used with
a map. In this case, the "add map" command must be used instead.
add map <map> <key> <value> add map [@<ver>] <map> <key> <value>
add map <map> <payload> add map [@<ver>] <map> <payload>
Add an entry into the map <map> to associate the value <value> to the key Add an entry into the map <map> to associate the value <value> to the key
<key>. This command does not verify if the entry already exists. It is <key>. This command does not verify if the entry already exists. It is
mainly used to fill a map after a clear operation. Note that if the reference mainly used to fill a map after a "clear" or "prepare" operation. Entries
<map> is a file and is shared with a map, this map will contain also a new are added to the current version of the ACL, unless a specific version is
pattern entry. Using the payload syntax it is possible to add multiple specified with "@<ver>". This version number must have preliminary been
key/value pairs by entering them on separate lines. On each new line, the allocated by "prepare acl", and it will be comprised between the versions
first word is the key and the rest of the line is considered to be the value reported in "curr_ver" and "next_ver" on the output of "show acl". Entries
which can even contains spaces. added with a specific version number will not match until a "commit map"
operation is performed on them. They may however be consulted using the
"show map @<ver>" command, and cleared using a "clear acl @<ver>" command.
If the designated map is also used as an ACL, the ACL will only match the
<key> part and will ignore the <value> part. Using the payload syntax it is
possible to add multiple key/value pairs by entering them on separate lines.
On each new line, the first word is the key and the rest of the line is
considered to be the value which can even contains spaces.
Example: Example:

View File

@ -796,6 +796,8 @@ static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx,
{ {
if (strcmp(args[1], "map") == 0 || if (strcmp(args[1], "map") == 0 ||
strcmp(args[1], "acl") == 0) { strcmp(args[1], "acl") == 0) {
const char *gen = NULL;
uint genid = 0;
int ret; int ret;
char *err; char *err;
@ -805,6 +807,15 @@ static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx,
else else
appctx->ctx.map.display_flags = PAT_REF_ACL; appctx->ctx.map.display_flags = PAT_REF_ACL;
/* For both "map" and "acl" we may have an optional generation
* number specified using a "@" character before the pattern
* file name.
*/
if (*args[2] == '@') {
gen = args[2] + 1;
args++;
}
/* If the keyword is "map", we expect: /* If the keyword is "map", we expect:
* - three parameters if there is no payload * - three parameters if there is no payload
* - one parameter if there is a payload * - one parameter if there is a payload
@ -829,6 +840,16 @@ static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx,
return cli_err(appctx, "Unknown ACL identifier. Please use #<id> or <file>.\n"); return cli_err(appctx, "Unknown ACL identifier. Please use #<id> or <file>.\n");
} }
if (gen) {
genid = str2uic(gen);
if ((int)(genid - appctx->ctx.map.ref->next_gen) > 0) {
if (appctx->ctx.map.display_flags == PAT_REF_MAP)
return cli_err(appctx, "Version number in the future, please use 'prepare map' before.\n");
else
return cli_err(appctx, "Version number in the future, please use 'prepare acl' before.\n");
}
}
/* The command "add acl" is prohibited if the reference /* The command "add acl" is prohibited if the reference
* use samples. * use samples.
*/ */
@ -880,7 +901,7 @@ static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx,
value = NULL; value = NULL;
HA_SPIN_LOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock); HA_SPIN_LOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock);
ret = !!pat_ref_load(appctx->ctx.map.ref, appctx->ctx.map.ref->curr_gen, key, value, -1, &err); ret = !!pat_ref_load(appctx->ctx.map.ref, gen ? genid : appctx->ctx.map.ref->curr_gen, key, value, -1, &err);
HA_SPIN_UNLOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock); HA_SPIN_UNLOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock);
if (!ret) { if (!ret) {
@ -1107,14 +1128,14 @@ static int cli_parse_commit_map(char **args, char *payload, struct appctx *appct
/* register cli keywords */ /* register cli keywords */
static struct cli_kw_list cli_kws = {{ },{ static struct cli_kw_list cli_kws = {{ },{
{ { "add", "acl", NULL }, "add acl : add acl entry", cli_parse_add_map, NULL }, { { "add", "acl", NULL }, "add acl [@ver] : add acl entry", cli_parse_add_map, NULL },
{ { "clear", "acl", NULL }, "clear acl [@ver] <id> : clear the content of this acl", cli_parse_clear_map, cli_io_handler_clear_map, NULL }, { { "clear", "acl", NULL }, "clear acl [@ver] <id> : clear the content of this acl", cli_parse_clear_map, cli_io_handler_clear_map, NULL },
{ { "commit","acl", NULL }, "commit acl @<ver> <id> : commit the ACL at this version", cli_parse_commit_map, cli_io_handler_clear_map, NULL }, { { "commit","acl", NULL }, "commit acl @<ver> <id> : commit the ACL at this version", cli_parse_commit_map, cli_io_handler_clear_map, NULL },
{ { "del", "acl", NULL }, "del acl : delete acl entry", cli_parse_del_map, NULL }, { { "del", "acl", NULL }, "del acl : delete acl entry", cli_parse_del_map, NULL },
{ { "get", "acl", NULL }, "get acl : report the patterns matching a sample for an ACL", cli_parse_get_map, cli_io_handler_map_lookup, cli_release_mlook }, { { "get", "acl", NULL }, "get acl : report the patterns matching a sample for an ACL", cli_parse_get_map, cli_io_handler_map_lookup, cli_release_mlook },
{ { "prepare","acl",NULL }, "prepare acl <id>: prepare a new version for atomic ACL replacement", cli_parse_prepare_map, NULL }, { { "prepare","acl",NULL }, "prepare acl <id>: prepare a new version for atomic ACL replacement", cli_parse_prepare_map, NULL },
{ { "show", "acl", NULL }, "show acl [@ver] [id] : report available acls or dump an acl's contents", cli_parse_show_map, NULL }, { { "show", "acl", NULL }, "show acl [@ver] [id] : report available acls or dump an acl's contents", cli_parse_show_map, NULL },
{ { "add", "map", NULL }, "add map : add map entry", cli_parse_add_map, NULL }, { { "add", "map", NULL }, "add map [@ver] : add map entry", cli_parse_add_map, NULL },
{ { "clear", "map", NULL }, "clear map [@ver] <id> : clear the content of this map", cli_parse_clear_map, cli_io_handler_clear_map, NULL }, { { "clear", "map", NULL }, "clear map [@ver] <id> : clear the content of this map", cli_parse_clear_map, cli_io_handler_clear_map, NULL },
{ { "commit","map", NULL }, "commit map @<ver> <id> : commit the map at this version", cli_parse_commit_map, cli_io_handler_clear_map, NULL }, { { "commit","map", NULL }, "commit map @<ver> <id> : commit the map at this version", cli_parse_commit_map, cli_io_handler_clear_map, NULL },
{ { "del", "map", NULL }, "del map : delete map entry", cli_parse_del_map, NULL }, { { "del", "map", NULL }, "del map : delete map entry", cli_parse_del_map, NULL },