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

Commit 7158392

Browse files
authored
Support etcd2 state provider (#105)
1 parent 6aabfc3 commit 7158392

File tree

7 files changed

+342
-12
lines changed

7 files changed

+342
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ README.md and examples/getting-started-app/README.md
1111
to use the newest tag with new release
1212
-->
1313

14+
### Added
15+
16+
- `etcd2` state provider for stateful failover (cartridge >= 2.2.0)
17+
1418
## [1.3.0] - 2020-05-08
1519

1620
### Added

README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,17 +446,35 @@ cartridge_failover_params:
446446

447447
**Note** that stateful failover is supported since `Cartridge` 2.1.2.
448448

449+
**Note** that `etcd2` provider is supported since `Cartridge` 2.2.0.
450+
449451
`stateful` failover requires these parameters:
450452

451453
- `state_provider`(`string`, required for `stateful` mode) - external state
452-
provider type. Now there's only one supported type - `stateboard`.
454+
provider type. Supported `stateboard` and `etcd2` providers.
453455

454456
- `stateboard_params`(`dict`, required for `stateboard` state provider) -
455457
configuration for stateboard:
456458
- `uri`(`string`, required) - stateboard instance URI;
457459

458460
- `password`(`string`, required) - stateboard instance password;
459461

462+
- `etcd2_params`(`dict`, used for for `etcd2` state provider) -
463+
configuration for stateboard:
464+
- `prefix`(`string`, optional) - prefix used for etcd keys: `<prefix>/lock` and
465+
`<prefix>/leaders`;
466+
467+
- `lock_delay`(`number`, optional) - timeout (in seconds), determines lock's
468+
time-to-live (default value in Cartridge is `10`);
469+
470+
- `entrypoints`(`string`, optional) - URIs that are used to discover and to access
471+
`etcd` cluster instances (default value in Cartridge is
472+
`['http://localhost:2379', 'http://localhost:4001']`);
473+
474+
- `username`(`string`, optional).
475+
476+
- `password`(`string`, optional).
477+
460478
Read [the doc](https://www.tarantool.io/en/doc/2.2/book/cartridge/cartridge_api/topics/failover.md/#stateful-failover)
461479
to learn more about stateful failover.
462480

@@ -477,9 +495,9 @@ cartridge_failover_params:
477495
`cartridge_auth` parameter is used to specify authorization settings:
478496

479497
- `enabled`(`boolean`, optional) - indicates if authorization is enabled;
480-
- `cookie_max_age`(`int`, optional) - number of seconds until the authorization
498+
- `cookie_max_age`(`number`, optional) - number of seconds until the authorization
481499
cookie expires;
482-
- `cookie_renew_age`(`int`, optional) - update the provided cookie if it's older
500+
- `cookie_renew_age`(`number`, optional) - update the provided cookie if it's older
483501
than this age.
484502
- `users`(`list-of-dicts`, optional) - list of users to be configured on the
485503
cluster (described below).

library/cartridge_manage_failover.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,32 @@ def manage_failover_new(control_console, failover_params):
6969

7070
lua_stateboard_params = []
7171
if stateboard_params is not None:
72-
if stateboard_params.get('uri') is not None:
73-
lua_stateboard_params.append('uri = "{}"'.format(stateboard_params['uri']))
74-
75-
if stateboard_params.get('password') is not None:
76-
lua_stateboard_params.append('password = "{}"'.format(stateboard_params['password']))
72+
for string_param in ['uri', 'password']:
73+
if stateboard_params.get(string_param) is not None:
74+
lua_stateboard_params.append('{} = "{}"'.format(string_param, stateboard_params[string_param]))
7775

7876
if lua_stateboard_params:
7977
lua_params.append('tarantool_params = {{ {} }}'.format(', '.join(lua_stateboard_params)))
78+
elif state_provider == 'etcd2':
79+
lua_params.append('state_provider = "etcd2"')
80+
81+
etcd2_params = failover_params.get('etcd2_params')
82+
83+
lua_etcd2_params = []
84+
if etcd2_params is not None:
85+
for string_param in ['prefix', 'username', 'password']:
86+
if etcd2_params.get(string_param) is not None:
87+
lua_etcd2_params.append('{} = "{}"'.format(string_param, etcd2_params[string_param]))
88+
89+
if etcd2_params.get('lock_delay') is not None:
90+
lua_etcd2_params.append('lock_delay = {}'.format(etcd2_params['lock_delay']))
91+
92+
if etcd2_params.get('endpoints') is not None:
93+
lua_etcd2_params.append('endpoints = {{ {} }}'.format(
94+
", ".join('"{}"'.format(endpoint) for endpoint in etcd2_params['endpoints'])
95+
))
96+
97+
lua_params.append('etcd2_params = {{ {} }}'.format(', '.join(lua_etcd2_params)))
8098

8199
res = control_console.eval('''
82100
local ok, err = require('cartridge').failover_set_params({{

library/cartridge_validate_config.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
STATEFUL_FAILOVER_PARAMS = [
4848
'state_provider',
4949
'stateboard_params',
50+
'etcd2_params',
5051
]
5152

5253
STATEFUL_FAILOVER_REQUIRED_PARAMS = [
@@ -55,13 +56,18 @@
5556

5657
STATEFUL_FAILOVER_STATE_PROVIDERS = [
5758
'stateboard',
59+
'etcd2',
5860
]
5961

6062
STATEBOARD_PROVIDER_REQUIRED_PARAMS = [
6163
'uri',
6264
'password',
6365
]
6466

67+
ETCD2_PROVIDER_REQUIRED_PARAMS = [
68+
'prefix',
69+
]
70+
6571
STATEBOARD_CONFIG_REQUIRED_PARAMS = [
6672
'listen',
6773
'password',
@@ -150,7 +156,14 @@ def validate_types(vars):
150156
'stateboard_params': {
151157
'uri': str,
152158
'password': str
153-
}
159+
},
160+
'etcd2_params': {
161+
'prefix': str,
162+
'lock_delay': int,
163+
'endpoints': [str],
164+
'username': str,
165+
'password': str,
166+
},
154167
}
155168
}
156169

@@ -391,6 +404,13 @@ def check_failover(found_common_params):
391404
'("{}" found)'.format(m.group())
392405
return errmsg
393406

407+
elif cartridge_failover_params['state_provider'] == 'etcd2':
408+
etcd2_params = cartridge_failover_params.get('etcd2_params')
409+
if etcd2_params is not None and etcd2_params.get('endpoints') is not None:
410+
for endpoint in etcd2_params['endpoints']:
411+
if not is_valid_advertise_uri(endpoint):
412+
return 'etcd2 endpoints must be specified as "<host>:<port>"'
413+
394414
return None
395415

396416

unit/mock/cartridge.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,10 @@ function cartridge.failover_set_params(params)
439439
vars.failover_params.tarantool_params = params.tarantool_params
440440
end
441441

442+
if params.etcd2_params ~= nil then
443+
vars.failover_params.etcd2_params = params.etcd2_params
444+
end
445+
442446
return true
443447
end
444448

unit/test_manage_failover.py

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313

1414

1515
def call_manage_failover(control_sock, mode,
16-
state_provider=None, stateboard_params=None):
16+
state_provider=None, stateboard_params=None, etcd2_params=None):
1717
return manage_failover({
1818
'control_sock': control_sock,
1919
'failover_params': {
2020
'mode': mode,
2121
'state_provider': state_provider,
2222
'stateboard_params': stateboard_params,
23+
'etcd2_params': etcd2_params,
2324
},
2425
})
2526

@@ -230,7 +231,7 @@ def test_stateful_failover_with_stateboard(self):
230231
self.instance.clear_calls('failover_set_params')
231232

232233
new_params = STATEBOARD_PARAMS.copy()
233-
new_params['uri'] = 'other value'
234+
new_params[p] = 'other-string-value'
234235

235236
res = call_manage_failover(self.console_sock, mode='stateful',
236237
state_provider='stateboard',
@@ -246,5 +247,182 @@ def test_stateful_failover_with_stateboard(self):
246247
'tarantool_params': new_params,
247248
})
248249

250+
def test_stateful_failover_with_etcd2(self):
251+
self.instance.set_cartridge_version('2.1.0')
252+
253+
ETCD2_PARAMS = {
254+
'prefix': '/',
255+
'lock_delay': 30,
256+
'username': 'dokshina',
257+
'password': 'secret',
258+
'endpoints': ['localhost:2379', 'localhost:2380']
259+
}
260+
261+
# failover disabled
262+
self.instance.set_variable('failover_params', {'mode': 'disabled'})
263+
self.instance.clear_calls('failover_set_params')
264+
265+
res = call_manage_failover(self.console_sock, mode='stateful',
266+
state_provider='etcd2',
267+
etcd2_params=ETCD2_PARAMS)
268+
self.assertTrue(res.success, msg=res.msg)
269+
self.assertTrue(res.changed)
270+
271+
calls = self.instance.get_calls('failover_set_params')
272+
self.assertEqual(len(calls), 1)
273+
self.assertEqual(calls[0], {
274+
'mode': 'stateful',
275+
'state_provider': 'etcd2',
276+
'etcd2_params': ETCD2_PARAMS,
277+
})
278+
279+
# failover disabled
280+
self.instance.set_variable('failover_params', {'mode': 'disabled'})
281+
self.instance.clear_calls('failover_set_params')
282+
283+
res = call_manage_failover(self.console_sock, mode='stateful',
284+
state_provider='etcd2',
285+
etcd2_params=None)
286+
self.assertTrue(res.success, msg=res.msg)
287+
self.assertTrue(res.changed)
288+
289+
calls = self.instance.get_calls('failover_set_params')
290+
self.assertEqual(len(calls), 1)
291+
self.assertEqual(calls[0], {
292+
'mode': 'stateful',
293+
'state_provider': 'etcd2',
294+
'etcd2_params': [],
295+
})
296+
297+
# stateful failover enabled - params aren't changed
298+
self.instance.set_variable('failover_params', {
299+
'mode': 'stateful',
300+
'state_provider': 'etcd2',
301+
'etcd2_params': ETCD2_PARAMS,
302+
})
303+
self.instance.clear_calls('failover_set_params')
304+
305+
res = call_manage_failover(self.console_sock, mode='stateful',
306+
state_provider='etcd2',
307+
etcd2_params=ETCD2_PARAMS)
308+
self.assertTrue(res.success, msg=res.msg)
309+
self.assertFalse(res.changed)
310+
311+
calls = self.instance.get_calls('failover_set_params')
312+
self.assertEqual(len(calls), 1)
313+
self.assertEqual(calls[0], {
314+
'mode': 'stateful',
315+
'state_provider': 'etcd2',
316+
'etcd2_params': ETCD2_PARAMS,
317+
})
318+
319+
for p in ['prefix', 'lock_delay', 'username', 'password', 'endpoints']:
320+
# stateful failover enabled - one param is changed
321+
self.instance.set_variable('failover_params', {
322+
'mode': 'stateful',
323+
'state_provider': 'etcd2',
324+
'etcd2_params': ETCD2_PARAMS,
325+
})
326+
self.instance.clear_calls('failover_set_params')
327+
328+
new_params = ETCD2_PARAMS.copy()
329+
if p in ['prefix', 'username', 'password']:
330+
new_params[p] = 'other-string-value'
331+
elif p in ['lock_delay']:
332+
new_params[p] = new_params[p] + 15
333+
elif p in ['endpoints']:
334+
new_params[p].append('localhost:2381')
335+
336+
res = call_manage_failover(self.console_sock, mode='stateful',
337+
state_provider='etcd2',
338+
etcd2_params=new_params)
339+
self.assertTrue(res.success, msg=res.msg)
340+
self.assertTrue(res.changed)
341+
342+
calls = self.instance.get_calls('failover_set_params')
343+
self.assertEqual(len(calls), 1)
344+
self.assertEqual(calls[0], {
345+
'mode': 'stateful',
346+
'state_provider': 'etcd2',
347+
'etcd2_params': new_params,
348+
})
349+
350+
def test_stateful_failover_mixed(self):
351+
self.instance.set_cartridge_version('2.1.0')
352+
353+
STATEBOARD_PARAMS = {
354+
'uri': 'localhost:3310',
355+
'password': 'passwd',
356+
}
357+
358+
ETCD2_PARAMS = {
359+
'prefix': '/',
360+
'lock_delay': 30,
361+
'username': 'dokshina',
362+
'password': 'secret',
363+
'endpoints': ['localhost:2379', 'localhost:2380']
364+
}
365+
366+
# failover disabled -> enable stateboard
367+
self.instance.set_variable('failover_params', {'mode': 'disabled'})
368+
self.instance.clear_calls('failover_set_params')
369+
370+
res = call_manage_failover(self.console_sock, mode='stateful',
371+
state_provider='stateboard',
372+
etcd2_params=ETCD2_PARAMS,
373+
stateboard_params=STATEBOARD_PARAMS)
374+
self.assertTrue(res.success, msg=res.msg)
375+
self.assertTrue(res.changed)
376+
377+
calls = self.instance.get_calls('failover_set_params')
378+
self.assertEqual(len(calls), 1)
379+
self.assertEqual(calls[0], {
380+
'mode': 'stateful',
381+
'state_provider': 'tarantool',
382+
'tarantool_params': STATEBOARD_PARAMS,
383+
})
384+
385+
# failover disabled -> enable etcd2
386+
self.instance.set_variable('failover_params', {'mode': 'disabled'})
387+
self.instance.clear_calls('failover_set_params')
388+
389+
res = call_manage_failover(self.console_sock, mode='stateful',
390+
state_provider='etcd2',
391+
etcd2_params=ETCD2_PARAMS,
392+
stateboard_params=STATEBOARD_PARAMS)
393+
self.assertTrue(res.success, msg=res.msg)
394+
self.assertTrue(res.changed)
395+
396+
calls = self.instance.get_calls('failover_set_params')
397+
self.assertEqual(len(calls), 1)
398+
self.assertEqual(calls[0], {
399+
'mode': 'stateful',
400+
'state_provider': 'etcd2',
401+
'etcd2_params': ETCD2_PARAMS,
402+
})
403+
404+
# stateboard state provider enabled -> switch to etcd2
405+
self.instance.set_variable('failover_params', {
406+
'mode': 'stateful',
407+
'state_provider': 'tarantool',
408+
'tarantool_params': STATEBOARD_PARAMS,
409+
})
410+
self.instance.clear_calls('failover_set_params')
411+
412+
res = call_manage_failover(self.console_sock, mode='stateful',
413+
state_provider='etcd2',
414+
etcd2_params=ETCD2_PARAMS,
415+
stateboard_params=STATEBOARD_PARAMS)
416+
self.assertTrue(res.success, msg=res.msg)
417+
self.assertTrue(res.changed)
418+
419+
calls = self.instance.get_calls('failover_set_params')
420+
self.assertEqual(len(calls), 1)
421+
self.assertEqual(calls[0], {
422+
'mode': 'stateful',
423+
'state_provider': 'etcd2',
424+
'etcd2_params': ETCD2_PARAMS,
425+
})
426+
249427
def tearDown(self):
250428
self.instance.stop()

0 commit comments

Comments
 (0)