[Tarantool-patches] [PATCH] box: set cfg via environment variables

Leonid Vasiliev lvasiliev at tarantool.org
Fri Mar 19 15:38:10 MSK 2021


Hi! Thank you for the patch.
See comments bellow:

On 3/14/21 6:34 PM, Roman Khabibov wrote:
> Add ability to set box.cfg options via env variables. These
> variables should have name "TT_<OPTION>". When Tarantool instance
> is started under tarantoolctl utility env variables options have
> more priority than instance file's cfg.
> 
> Closes #5602
> 
> @TarantoolBot document
> Title: Set box.cfg options via env variables
> 
> Now, it is possible to set box.cfg options via environment
> variables. The name of variable should correspond the followng

1) followng -> following

> pattern: "TT_<NAME>", where <NAME> is an option name. For example:
> "TT_LISTEN", "TT_READAHEAD".
> If it is complex value, for example, "replication" can be set by a
> table, the corresponding environment varibale value has the syntax

2) varibale -> variable

> of lua array (without keys):
> 
> `export TT_REPLICATION="{'uri1', 'uri2'}"`
> 
> Note that every value in arary must be quoted.

1) arary -> array

> ---
> 
> @ChangeLog
> * Add ability to set box.cfg options via environment variables (gh-5602).
> 
> Branch: https://github.com/tarantool/tarantool/tree/romanhabibov/gh-5602-environment-variables
> Issue: https://github.com/tarantool/tarantool/issues/5602
> 
>   src/box/lua/load_cfg.lua                      | 74 +++++++++++++++++++
>   test/app-tap/tarantoolctl.test.lua            | 33 ++++++++-
>   .../gh-5602-environment-vars-cfg.result       | 42 +++++++++++
>   .../gh-5602-environment-vars-cfg.test.lua     | 53 +++++++++++++
>   test/box-tap/gh-5602_script.lua               | 70 ++++++++++++++++++
>   5 files changed, 271 insertions(+), 1 deletion(-)
>   create mode 100644 test/box-tap/gh-5602-environment-vars-cfg.result
>   create mode 100755 test/box-tap/gh-5602-environment-vars-cfg.test.lua
>   create mode 100755 test/box-tap/gh-5602_script.lua
> 
> diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
> index 574c8bef4..eace25d46 100644
> --- a/src/box/lua/load_cfg.lua
> +++ b/src/box/lua/load_cfg.lua
> @@ -616,6 +616,79 @@ setmetatable(box, {
>        end
>   })
> 

4) Add descriptions to functions (must start with `---`).

> +local function process_option_value(option, raw_value)
> +    local param_type = template_cfg[option]
> +    if param_type == nil then
> +        return nil
> +    end
> +    local value = nil
> +    if param_type == 'number' or option == 'log_level' then
> +        value = tonumber(raw_value)
> +        if value == nil then
> +            error("Environment variable "..'TT_'..string.upper(option).." has"..
> +                  " incorrect value for option '"..option.."': should be "..
> +                  "converted to number")

5) Codestyle (apply to the whole patch):
     - Indetation.
         From https://dev.minetest.net/Lua_code_style_guidelines :
         "When strings don't fit into the line, you should add the string
         (changes) to the next line(s) indented by one tab." (4 spaces 
in our case).
     - String format.
         From 
https://www.tarantool.io/en/doc/latest/dev_guide/lua_style_guide/#indentation-and-formatting:
         "Avoid multiple concatenations in one statement, use 
string.format instead"

> +        end
> +    elseif param_type == 'boolean' or option == 'log_nonblock' then
> +        if raw_value == 'true' then
> +            value = true
> +        elseif raw_value == 'false' then
> +            value = false
> +        else
> +            error("Environment variable "..'TT_'..string.upper(option).." has"..
> +                  " incorrect value for option '"..option.."': should be "..
> +                  "'true' or 'false'")
> +        end
> +    elseif param_type == 'string, number' then
> +        value = tonumber(raw_value)
> +        if value == nil then
> +            value = raw_value
> +        end
> +    elseif param_type == 'string, number, table' then

6) OMG! We have to get the table from the env variable. Nothing good to 
expect.
     - What about a nested table?
     - Also, what about the cases "string, table" or "table, string, 
number" ...? Now we don't have such a `param_type`, but they will appear 
tomorrow.

> +        if string.sub(raw_value, 1, 1) == '{' and
> +           string.sub(raw_value, -1) == '}' then
> +            value = string.sub(raw_value, 2, -2)
> +            value = require('csv').load(value)[1]
> +            for i, str in pairs(value) do
> +                if not (string.sub(str, 1, 1) == '\'' and
> +                        string.sub(str, -1) == '\'') and
> +                   not (string.sub(str, 1, 1) == '"' and
> +                        string.sub(str, -1) == '"') then
> +                    error("Environment variable "..'TT_'..string.upper(option)..
> +                          " has incorrect value for option '"..option.."': "..
> +                          "elements of array should be quoted strings")
> +                end
> +                str = string.sub(str, 2, -2)

7) Shouldn't we be checking types inside a table? Or can there only be 
strings?

> +                value[i] = str
> +            end
> +        else
> +            value = tonumber(raw_value)
> +            if value == nil then
> +                value = raw_value
> +            end
> +        end
> +    else
> +        value = raw_value
> +    end
> +
> +    return value
> +end
> +
> +local function collect_env_cfg(cfg)
> +    if cfg == nil then
> +        cfg = {}
> +    end

8) Please, separate logical blocks of code from each other with empty 
lines. Write comments.(apply to the whole patch).

> +    for option, _ in pairs(template_cfg) do
> +        if cfg[option] == nil or os.getenv('TARANTOOLCTL') == 'true' then
> +            local env_var_name = 'TT_'..string.upper(option)
> +            local raw_value = os.getenv(env_var_name)
> +            if raw_value ~= nil then
> +                cfg[option] = process_option_value(option, raw_value)
> +            end
> +        end
> +    end
> +end
> +
>   -- Whether box is loaded.
>   --
>   -- `false` when box is not configured or when the initialization
> @@ -627,6 +700,7 @@ setmetatable(box, {
>   local box_is_configured = false
>   
>   local function load_cfg(cfg)
> +    collect_env_cfg(cfg)
>       -- A user may save box.cfg (this function) before box loading
>       -- and call it afterwards. We should reconfigure box in the
>       -- case.

9) It looks like in this case we can overload the value that was set in
the previous call with the value from the environment. Or not?

> diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
> index 3c3023d34..1f15fe103 100755
> --- a/test/app-tap/tarantoolctl.test.lua
> +++ b/test/app-tap/tarantoolctl.test.lua
> @@ -160,7 +160,7 @@ local function merge(...)
>   end
>   
>   local test = tap.test('tarantoolctl')
> -test:plan(8)
> +test:plan(9)
>   
>   -- basic start/stop test
>   -- must be stopped afterwards
> @@ -632,4 +632,35 @@ test:test('filter_xlog', function(test)
>       end
>   end)
>   
> +-- gh-5602: Check that environment cfg variables are more
> +-- prioritized than tarantoolctl cfg.
> +do

10) As for me, it is preferable to use `test:test()` instead of 
`do...end`. Up to you.

> +    local dir = fio.tempdir()
> +    local code = [[ box.cfg ]]

11) Maybe `[[ box.cfg{} ]]`

> +    create_script(dir, 'show_cfg.lua',  code)

12) Two spaces before `code`, but one is needed.

> +    local code = [[ box.cfg{listen=3302, readahead=10001} ]]
> +    create_script(dir, 'cfg_script.lua', code)
> +
> +    local status, err = pcall(function()
> +        test:test("check answers in case of call", function(test_i)
> +            test_i:plan(4)
> +            os.setenv('TT_LISTEN', '3301')

13) We will not have problems due to a specific port when many tests are
run in parallel (such as this port will be already occupied).

> +            os.setenv('TT_READAHEAD', '10000')
> +            check_ok(test_i, dir, 'start', 'cfg_script', 0)
> +            tctl_wait_start(dir, 'cfg_script')
> +            check_ok(test_i, dir, 'eval',  'cfg_script show_cfg.lua', 0,
> +                     '---\n- 3301\n- 10000\n...', nil)
> +            check_ok(test_i, dir, 'stop', 'cfg_script', 0)
> +        end)
> +    end)
> +
> +    cleanup_instance(dir, 'cfg_script')
> +    recursive_rmdir(dir)
> +
> +    if status == false then
> +        print(("Error: %s"):format(err))
> +        os.exit()
> +    end
> +end
> +
>   os.exit(test:check() == true and 0 or -1)
> diff --git a/test/box-tap/gh-5602-environment-vars-cfg.result b/test/box-tap/gh-5602-environment-vars-cfg.result
> new file mode 100644
> index 000000000..495e02a73
> --- /dev/null
> +++ b/test/box-tap/gh-5602-environment-vars-cfg.result
> @@ -0,0 +1,42 @@
> +TAP version 13
> +1..5
> +ok - box.cfg is successful
> +ok - listen
> +ok - readahead
> +ok - strip_core
> +ok - log_format is not set
> +TAP version 13
> +1..7
> +ok - box.cfg is successful
> +ok - listen
> +ok - replication is table
> +ok - replication URI 1
> +ok - replication URI 2
> +ok - replication_connect_timeout
> +ok - replication_synchro_quorum
> +TAP version 13
> +1..3
> +ok - box.cfg is successful
> +ok - box.cfg{} background value is prioritized
> +ok - box.cfg{} vinyl_timeout value is prioritized
> +TAP version 13
> +1..2
> +ok - box.cfg is not successful
> +ok - bad sql_cache_size value
> +TAP version 13
> +1..2
> +ok - box.cfg is not successful
> +builtin/box/load_cfg.lua:19: builtin/box/load_cfg.lua:638: Environment variable TT_STRIP_CORE has incorrect value for option 'strip_core': should be 'true' or 'false'
> +ok - bad strip_core value
> +TAP version 13
> +1..2
> +ok - box.cfg is not successful
> +builtin/box/load_cfg.lua:19: builtin/box/load_cfg.lua:657: Environment variable TT_REPLICATION has incorrect value for option 'replication': elements of array should be quoted strings
> +ok - bad replication value
> +TAP version 13
> +ok - set 1
> +ok - set 2
> +ok - set 3
> +ok - set 4
> +ok - set 5
> +ok - set 6
> diff --git a/test/box-tap/gh-5602-environment-vars-cfg.test.lua b/test/box-tap/gh-5602-environment-vars-cfg.test.lua
> new file mode 100755
> index 000000000..84b49b7eb
> --- /dev/null
> +++ b/test/box-tap/gh-5602-environment-vars-cfg.test.lua
> @@ -0,0 +1,53 @@
> +#!/usr/bin/env tarantool
> +
> +local os = require('os')
> +local fio = require('fio')
> +local tap = require('tap')

13) It's nice to separate the variable definition and the `requires` 
with empty line.(apply to the whole patch)

> +local test = tap.test('gh-5602')
> +
> +-- gh-5602: Check that environment cfg variables working.
> +local TARANTOOL_PATH = arg[-1]
> +local script_name = 'gh-5602_script.lua'
> +local path_to_script = fio.pathjoin(
> +        os.getenv('PWD'),
> +        'box-tap',
> +        script_name)

14) Please, separate the function definition with an empty line.

15) Describe the test mechanism using a comment.

> +local function shell_command(set, i)
> +    string.gsub(set, 'TT_', '')
> +    return ('%s %s %s %d'):format(
> +        set,
> +        TARANTOOL_PATH,
> +        path_to_script,
> +        i)
> +end
> +
> +local set_1 = 'TT_LISTEN=3301 ' ..
> +              'TT_READAHEAD=10000 ' ..
> +              'TT_STRIP_CORE=false ' ..
> +              'TT_LOG_FORMAT=json'
> +local set_2 = 'TT_LISTEN=3301 ' ..
> +              'TT_REPLICATION=\"{\'0.0.0.0:12345\', ' ..
> +                              '\'1.1.1.1:12345\'}\" ' ..
> +              'TT_REPLICATION_CONNECT_TIMEOUT=0.01 ' ..
> +              'TT_REPLICATION_SYNCHRO_QUORUM=\'4 + 1\''
> +local set_3 = 'TT_BACKGROUND=true ' ..
> +              'TT_VINYL_TIMEOUT=60.1'
> +local set_4 = 'TT_SQL_CACHE_SIZE=a'
> +local set_5 = 'TT_STRIP_CORE=a'
> +local set_6 = 'TT_REPLICATION=\"{0.0.0.0:12345\', ' ..
> +                              '\'1.1.1.1:12345\'}\"'
> +local sets = {}
> +sets[1] = set_1
> +sets[2] = set_2
> +sets[3] = set_3
> +sets[4] = set_4
> +sets[5] = set_5
> +sets[6] = set_6
> +
> +for i, set in ipairs(sets) do
> +    local tmpdir = fio.tempdir()
> +    local new_path = fio.pathjoin(tmpdir, script_name)
> +    fio.copyfile(path_to_script, new_path)
> +    test:is(os.execute(shell_command(set, i)), 0, 'set '..i)
> +end

16) Add `test:plan()` and `test:check()`.

> +os.exit(0)
> diff --git a/test/box-tap/gh-5602_script.lua b/test/box-tap/gh-5602_script.lua

17) Use a more self-documenting name.

> new file mode 100755
> index 000000000..8ff34deb2
> --- /dev/null
> +++ b/test/box-tap/gh-5602_script.lua
> @@ -0,0 +1,70 @@
> +local tap = require('tap')
> +local test = tap.test('gh-5602')
> +
> +local status, err = pcall(box.cfg, {background = false, vinyl_timeout = 70.1})
> +
> +-- Check that environment cfg values are set correctly.
> +if arg[1] == '1' then
> +    test:plan(5)
> +    test:ok(status ~= false, 'box.cfg is successful')
> +    test:is(box.cfg['listen'], '3301', 'listen')
> +    test:is(box.cfg['readahead'], 10000, 'readahead')
> +    test:is(box.cfg['strip_core'], false, 'strip_core')
> +    test:is(box.cfg['log_format'], 'json', 'log_format is not set')
> +end
> +if arg[1] == '2' then
> +    test:plan(7)
> +    test:ok(status ~= false, 'box.cfg is successful')
> +    test:is(box.cfg['listen'], '3301', 'listen')
> +    local replication = box.cfg['replication']
> +    test:is(type(replication), 'table', 'replication is table')
> +    test:ok(replication[1] == '0.0.0.0:12345' or
> +            replication[1] == '1.1.1.1:12345', 'replication URI 1')
> +    test:ok(replication[2] == '0.0.0.0:12345' or
> +            replication[2] == '1.1.1.1:12345', 'replication URI 2')
> +    test:is(box.cfg['replication_connect_timeout'], 0.01,
> +            'replication_connect_timeout')
> +    test:is(box.cfg['replication_synchro_quorum'], '4 + 1',
> +            'replication_synchro_quorum')
> +end
> +
> +-- Check that box.cfg{} values are more prioritized than
> +-- environment cfg values.
> +if arg[1] == '3' then
> +    test:plan(3)
> +    test:ok(status ~= false, 'box.cfg is successful')
> +    test:is(box.cfg['background'], false,
> +            'box.cfg{} background value is prioritized')
> +    test:is(box.cfg['vinyl_timeout'], 70.1,
> +            'box.cfg{} vinyl_timeout value is prioritized')
> +end
> +
> +-- Check bad environment cfg values.
> +if arg[1] == '4' then
> +    test:plan(2)
> +    test:ok(status == false, 'box.cfg is not successful')
> +    local index = string.find(err, 'Environment variable TT_SQL_CACHE_SIZE '..
> +        'has incorrect value for option \'sql_cache_size\': should be '..
> +        'converted to number')
> +    test:ok(index ~= nil, 'bad sql_cache_size value')
> +end
> +if arg[1] == '5' then
> +    test:plan(2)
> +    test:ok(status == false, 'box.cfg is not successful')
> +    print(err)
> +    local index = string.find(err, 'Environment variable TT_STRIP_CORE has '..
> +        'incorrect value for option \'strip_core\': should be \'true\' '..
> +        'or \'false\'')
> +    test:ok(index ~= nil, 'bad strip_core value')
> +end
> +if arg[1] == '6' then
> +    test:plan(2)
> +    test:ok(status == false, 'box.cfg is not successful')
> +    print(err)
> +    local index = string.find(err, 'Environment variable TT_REPLICATION has '..
> +        'incorrect value for option \'replication\': elements of array should'..
> +        ' be quoted strings')
> +    test:ok(index ~= nil, 'bad replication value')
> +end
> +
> +os.exit(test:check() and 0 or 1)
> \ No newline at end of file

18) Add newline to the end of file.

> 


More information about the Tarantool-patches mailing list