Skip to content
This repository was archived by the owner on Jan 1, 2024. It is now read-only.

Commit b950ef9

Browse files
authored
Add a step to patch instance parameters in runtime (#341)
Closes #253 Sometimes we want to change some `box.cfg` parameters in runtime only. Now, it's possible to do like this: ```yaml - name: Set replication_connect_quorum to zero hosts: "*storage*" become: true gather_facts: no roles: - tarantool.cartridge vars: cartridge_scenario: - patch_instance_in_runtime cartridge_runtime_params: replication_connect_quorum: 0 ```
1 parent 277ea60 commit b950ef9

21 files changed

+454
-178
lines changed

.github/helpers/count_molecule_matrix.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def main(event_name, repo_owner, review_state, ref):
5454
ce_matrix.append(get_ce_params(molecule_scenario='eval'))
5555
ce_matrix.append(get_ce_params(molecule_scenario='needs_restart'))
5656
ce_matrix.append(get_ce_params(molecule_scenario='package_name'))
57+
ce_matrix.append(get_ce_params(molecule_scenario='patch_instance'))
5758
ce_matrix.append(get_ce_params(molecule_scenario='rolling_update'))
5859
ce_matrix.append(get_ce_params(molecule_scenario='start_stop'))
5960
ce_matrix.append(get_ce_params(molecule_scenario='tasks_from'))

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ README.md to use the newest tag with new release
2323
- availability to upload ZIP configs to TDG
2424
- `cartridge_not_save_cookie_in_app_config` variable that allows to disable persisting cluster
2525
cookie in the application configuration file
26+
- `patch_instance_in_runtime` step to update instance parameters in runtime
2627

2728
## [1.9.0] - 2021-04-30
2829

defaults/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ cartridge_keep_num_latest_dists: 2
9696

9797
cartridge_defaults: {}
9898
config: null
99+
cartridge_runtime_params: null
99100
zone: null
100101

101102
restarted: null

doc/scenario.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ There are additional steps that are not included in the default scenario, but ca
5454
- [stop_instance](#stop_instance)
5555
- [start_instance](#start_instance)
5656
- [restart_instance_force](#restart_instance_force)
57+
- [patch_instance_in_runtime](#patch_instance_in_runtime)
5758

5859
To replace the steps of the role with your own or add new steps, you should use `cartridge_custom_steps_dir`
5960
or `cartridge_custom_steps` options (see [examples](#examples)).
@@ -824,3 +825,22 @@ Restart and enable instance systemd service without any conditions.
824825
Input facts (set by role):
825826

826827
- `instance_info` - information for a current instance ([more details here](#role-facts-descriptions)).
828+
829+
### patch_instance_in_runtime
830+
831+
Patch dynamic (see [parameters](https://www.tarantool.io/en/doc/latest/reference/configuration/#configuration-parameters)
832+
with `Dynamic: yes`) instance parameters in runtime only
833+
(now it's possible to change only `box` config parameters).
834+
If the none-dynamic parameter is specified,
835+
nothing will be changed, and an error will be returned.
836+
837+
**Note** that memory size can be only increased in runtime.
838+
839+
Input facts (set by role):
840+
841+
- `instance_info` - information for a current instance ([more details here](#role-facts-descriptions)).
842+
843+
Input facts (set by config):
844+
845+
- `cartridge_runtime_params` - new instance parameters ([more details here](/doc/instances.md));
846+
- `expelled` - indicates if instance must be expelled from topology.

doc/variables.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,13 @@ For more details see [scenario documentation](/doc/scenario.md).
7878

7979
## Instances configuration
8080

81+
* `cartridge_defaults` (`dict`, default: `{}`): default configuration
82+
parameters values for instances;
8183
* `config` (`dict`, required): [instance configuration](/doc/instances.md);
8284
* `zone` (`string`): instance zone (available since
8385
[Cartridge 2.4.0](https://github.com/tarantool/cartridge/releases/tag/2.4.0));
84-
* `cartridge_defaults` (`dict`, default: `{}`): default configuration
85-
parameters values for instances;
86+
* `cartridge_runtime_params` (`dict`): [instance configuration](/doc/instances.md)
87+
parameters to patch in runtime;
8688
* `restarted` (`boolean`): flag indicates if instance should be
8789
restarted or not (if this flag isn't specified, instance will be restarted if
8890
it's needed to apply configuration changes);

library/cartridge_get_cached_facts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
'cartridge_app_user',
1818
'cartridge_auth',
1919
'cartridge_bootstrap_vshard',
20+
'cartridge_runtime_params',
2021
'cartridge_cluster_cookie',
2122
'cartridge_not_save_cookie_in_app_config',
2223
'cartridge_conf_dir',

library/cartridge_instance.py

Lines changed: 0 additions & 136 deletions
This file was deleted.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env python
2+
3+
from copy import deepcopy
4+
5+
from ansible.module_utils.helpers import Helpers as helpers
6+
7+
argument_spec = {
8+
'console_sock': {'required': True, 'type': 'str'},
9+
'instance_config': {'required': True, 'type': 'dict'},
10+
'cartridge_defaults': {'required': False, 'type': 'dict', 'default': {}},
11+
'strict_mode': {'required': False, 'type': 'bool', 'default': False},
12+
}
13+
14+
15+
def change_memory_size(control_console, param_name, new_value):
16+
if new_value is None:
17+
return False, None
18+
19+
increase_memory_func_body = '''
20+
local param_name, new_memory_size = ...
21+
local ok, err = pcall(box.cfg, { [param_name] = new_memory_size })
22+
if not ok then
23+
if tostring(err):find("cannot decrease memory size at runtime") == nil then
24+
return nil, string.format('failed to set %s: %s', param_name, err)
25+
end
26+
end
27+
return ok
28+
'''
29+
return control_console.eval_res_err(increase_memory_func_body, param_name, new_value)
30+
31+
32+
def change_dynamic_params(control_console, old_box_config, new_config, strict_mode=False):
33+
memory_sizes_to_change = {}
34+
box_params_to_change = {}
35+
incorrect_memory_changes = []
36+
non_dynamic_params = []
37+
38+
for param_name, new_value in new_config.items():
39+
old_box_value = old_box_config.get(param_name)
40+
41+
if param_name in helpers.MEMORY_SIZE_BOX_CFG_PARAMS:
42+
if new_value > old_box_value:
43+
memory_sizes_to_change.update({param_name: new_value})
44+
else:
45+
incorrect_memory_changes.append((param_name, old_box_value, new_value))
46+
47+
elif param_name in helpers.DYNAMIC_BOX_CFG_PARAMS:
48+
if new_value != old_box_value:
49+
box_params_to_change.update({param_name: new_value})
50+
51+
else:
52+
non_dynamic_params.append(param_name)
53+
54+
if strict_mode:
55+
messages = []
56+
if incorrect_memory_changes:
57+
messages.append('impossible to decrease memory sizes in runtime (%s)' % ', '.join(
58+
map(lambda change_info: "'%s' from '%s' to '%s'" % change_info, incorrect_memory_changes)
59+
))
60+
if non_dynamic_params:
61+
messages.append("impossible to change '%s' in runtime" % ', '.join(non_dynamic_params))
62+
if messages:
63+
return None, "Impossible to patch instance config: %s" % '; '.join(messages)
64+
65+
memory_changed = False
66+
for param_name, new_value in memory_sizes_to_change.items():
67+
changed, err = change_memory_size(control_console, param_name, new_value)
68+
if err is not None:
69+
return None, 'Failed to change memory size in runtime: %s' % err
70+
if changed:
71+
memory_changed = True
72+
73+
box_changed = False
74+
if box_params_to_change:
75+
update_box_cfg_func_body = '''
76+
box.cfg(...)
77+
return box.cfg
78+
'''
79+
updated_config, _ = control_console.eval_res_err(update_box_cfg_func_body, box_params_to_change)
80+
box_changed = old_box_config != updated_config
81+
82+
return memory_changed or box_changed, None
83+
84+
85+
def configure_box_cfg_params(console_sock, instance_config, defaults=None, strict_mode=False):
86+
control_console, err = helpers.get_control_console_if_started(console_sock, strict_mode)
87+
if err is not None:
88+
return None, err
89+
if not control_console:
90+
return False, None
91+
92+
if not helpers.box_cfg_was_called(control_console):
93+
if strict_mode:
94+
return None, "Impossible to patch instance config: 'box.cfg' wasn't called"
95+
return False, None
96+
97+
old_box_config = helpers.get_box_cfg(control_console)
98+
new_config = deepcopy(defaults or {})
99+
new_config.update(instance_config)
100+
101+
return change_dynamic_params(control_console, old_box_config, new_config, strict_mode)
102+
103+
104+
def patch_instance_in_runtime(params):
105+
console_sock = params['console_sock']
106+
instance_config = params['instance_config']
107+
cartridge_defaults = params['cartridge_defaults']
108+
strict_mode = params['strict_mode']
109+
110+
changed, error = configure_box_cfg_params(console_sock, instance_config, cartridge_defaults, strict_mode)
111+
if error is not None:
112+
return helpers.ModuleRes(failed=True, msg=error)
113+
return helpers.ModuleRes(changed=changed)
114+
115+
116+
if __name__ == '__main__':
117+
helpers.execute_module(argument_spec, patch_instance_in_runtime)

library/cartridge_validate_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
'cartridge_app_name': str,
9494
'cartridge_cluster_cookie': str,
9595
'cartridge_not_save_cookie_in_app_config': bool,
96+
'cartridge_runtime_params': dict,
9697
'cartridge_defaults': dict,
9798
'cartridge_bootstrap_vshard': bool,
9899
'cartridge_wait_buckets_discovery': bool,

module_utils/helpers.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,13 @@ def __init__(self, code, message):
275275
super(CartridgeException, self).__init__(message)
276276
self.code = code
277277

278+
def is_instance_not_started_error(self):
279+
return self.code in [
280+
CartridgeErrorCodes.SOCKET_NOT_FOUND,
281+
CartridgeErrorCodes.FAILED_TO_CONNECT_TO_SOCKET,
282+
CartridgeErrorCodes.INSTANCE_IS_NOT_STARTED_YET
283+
]
284+
278285

279286
class Console:
280287
def __init__(self, socket_path):
@@ -437,6 +444,22 @@ def get_control_console(socket_path):
437444
return Console(socket_path)
438445

439446

447+
def get_control_console_if_started(console_sock, strict_mode=False):
448+
if not os.path.exists(console_sock):
449+
if strict_mode:
450+
return None, "console socket '%s' doesn't exists" % console_sock
451+
return None, None
452+
453+
try:
454+
return get_control_console(console_sock), None
455+
except CartridgeException as e:
456+
if not strict_mode and e.is_instance_not_started_error():
457+
# Impossible to connect to socket
458+
return None, None
459+
460+
return None, str(e)
461+
462+
440463
def is_expelled(host_vars):
441464
return host_vars.get('expelled') is True
442465

@@ -597,6 +620,7 @@ class Helpers:
597620
warn = staticmethod(warn)
598621
execute_module = staticmethod(execute_module)
599622
get_control_console = staticmethod(get_control_console)
623+
get_control_console_if_started = staticmethod(get_control_console_if_started)
600624
is_expelled = staticmethod(is_expelled)
601625
is_stateboard = staticmethod(is_stateboard)
602626
get_instance_id = staticmethod(get_instance_id)

0 commit comments

Comments
 (0)