From bb51c44d64c59ee2c39a9fe10beb78b7815b292b Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 30 Apr 2021 15:23:36 +0200 Subject: [PATCH] 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. --- doc/management.txt | 38 ++++++++++++++++++++++++++------------ src/map.c | 27 ++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/doc/management.txt b/doc/management.txt index 89841bdc08..bba88c0fd6 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -1397,22 +1397,36 @@ abort ssl cert See also "set ssl cert" and "commit ssl cert". -add acl +add acl [@] Add an entry into the acl . is the # or the returned by - "show acl". This command does not verify if the entry already exists. This - command cannot be used if the reference is a file also used with a map. - In this case, you must use the command "add map" in place of "add acl". + "show acl". This command does not verify if the entry already exists. Entries + are added to the current version of the ACL, unless a specific version is + specified with "@". 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 @" command, and cleared using a "clear acl @" command. + This command cannot be used if the reference is a file also used with + a map. In this case, the "add map" command must be used instead. -add map -add map +add map [@] +add map [@] Add an entry into the map to associate the value to the 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 - is a file and is shared with a map, this map will contain also a new - pattern entry. 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. + mainly used to fill a map after a "clear" or "prepare" operation. Entries + are added to the current version of the ACL, unless a specific version is + specified with "@". 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 map" + operation is performed on them. They may however be consulted using the + "show map @" command, and cleared using a "clear acl @" command. + If the designated map is also used as an ACL, the ACL will only match the + part and will ignore the 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: diff --git a/src/map.c b/src/map.c index d2781c2ae3..83c14bc194 100644 --- a/src/map.c +++ b/src/map.c @@ -796,6 +796,8 @@ static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx, { if (strcmp(args[1], "map") == 0 || strcmp(args[1], "acl") == 0) { + const char *gen = NULL; + uint genid = 0; int ret; char *err; @@ -805,6 +807,15 @@ static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx, else 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: * - three parameters if there is no 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 # or .\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 * use samples. */ @@ -880,7 +901,7 @@ static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx, value = NULL; 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); if (!ret) { @@ -1107,14 +1128,14 @@ static int cli_parse_commit_map(char **args, char *payload, struct appctx *appct /* register cli keywords */ 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] : clear the content of this acl", cli_parse_clear_map, cli_io_handler_clear_map, NULL }, { { "commit","acl", NULL }, "commit acl @ : 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 }, { { "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 : 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 }, - { { "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] : clear the content of this map", cli_parse_clear_map, cli_io_handler_clear_map, NULL }, { { "commit","map", NULL }, "commit map @ : 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 },