Skip to content

Commit 1b81db9

Browse files
committed
config: switch to RO in isolated mode
The isolated instance shouldn't produce new transactions, including ones from a background fiber started by an application or a role. So, the isolated instance forcefully goes to the read-only mode disregarding any other configuration options. Part of tarantool#10776 NO_DOC=tarantool/doc#4632 NO_CHANGELOG=added together with the configuration option
1 parent bc632d0 commit 1b81db9

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed

src/box/lua/config/applier/box_cfg.lua

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,39 @@ local function peer_uris(configdata)
6161
return uris
6262
end
6363

64+
-- Modify box-level configuration values and perform other actions
65+
-- to enable the isolated mode (if configured).
66+
local function switch_isolated_mode(configdata, box_cfg)
67+
-- If the isolated mode is not enabled, there is nothing to do.
68+
if not configdata:get('isolated', {use_default = true}) then
69+
return
70+
end
71+
72+
-- An application or a role may perform background database
73+
-- modification if the instance is in the RW mode: for
74+
-- example, a role may perform eviction of stale records.
75+
-- If the instance is in the isolated mode, it should be in RO
76+
-- to don't produce any new transactions.
77+
--
78+
-- The reason is that these transactions will be sent to other
79+
-- replicaset members and applied on them, when the instance
80+
-- goes from the isolated mode. At the same time, the
81+
-- non-isolated part of the replicaset may serve requests and
82+
-- perform data modifications. An attempt to modify the same
83+
-- data from two instances may break data integrity[^1].
84+
--
85+
-- It is recommended to extract all the needed data from the
86+
-- isolated instance and perform the modifications on the
87+
-- current leader (in the non-isolated part of the
88+
-- replicaset).
89+
--
90+
-- [^1]: Unless the data operations are carefully designed to
91+
-- be idempotent to use in the master-master mode.
92+
--
93+
-- TODO(gh-10404): Set ro_reason=isolated.
94+
box_cfg.read_only = true
95+
end
96+
6497
local function log_destination(log)
6598
if log.to == 'stderr' or log.to == 'devnull' then
6699
return box.NULL
@@ -754,6 +787,10 @@ local function apply(config)
754787
box_cfg.read_only = true
755788
end
756789

790+
-- RO may be enforced by the isolated mode, so we call the
791+
-- function after all the other RO/RW logic is done.
792+
switch_isolated_mode(configdata, box_cfg)
793+
757794
log.debug('box_cfg.apply (startup)')
758795
box.cfg(box_cfg)
759796

@@ -762,6 +799,10 @@ local function apply(config)
762799
return {needs_retry = startup_may_be_long}
763800
end
764801

802+
-- RO may be enforced by the isolated mode, so we call the
803+
-- function after all the other RO/RW logic is done.
804+
switch_isolated_mode(configdata, box_cfg)
805+
765806
-- If it is reload, just apply the new configuration.
766807
log.debug('box_cfg.apply')
767808
box.cfg(box_cfg)

test/config-luatest/isolated_mode_test.lua

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,62 @@ g.test_startup_with_snap = function(g)
9393
g.it:roundtrip("box.space._schema:get({'replicaset_name'})[2]",
9494
'replicaset-001')
9595
end
96+
97+
-- Verify that the instance goes to RO in the isolated mode
98+
-- disregarding of any other configuration options.
99+
--
100+
-- The test case verifies that it occurs on a runtime
101+
-- reconfiguration as well as on a startup.
102+
g.test_read_only = function(g)
103+
local function assert_isolated(exp)
104+
g.it:roundtrip("require('config'):get('isolated')", exp)
105+
end
106+
107+
local function assert_read_only(exp)
108+
g.it:roundtrip('box.info.ro', exp)
109+
end
110+
111+
local config = cbuilder:new()
112+
:set_replicaset_option('replication.failover', 'manual')
113+
:set_replicaset_option('leader', 'i-001')
114+
:add_instance('i-001', {})
115+
:add_instance('i-002', {})
116+
:add_instance('i-003', {})
117+
:config()
118+
119+
local cluster = cluster.new(g, config)
120+
cluster:start()
121+
122+
-- Mark i-001 as isolated, reload the configuration.
123+
local config_2 = cbuilder:new(config)
124+
:set_instance_option('i-001', 'isolated', true)
125+
:config()
126+
cluster:reload(config_2)
127+
128+
-- Use the console connection, because an instance in the
129+
-- isolated mode doesn't accept iproto requests.
130+
g.it = it.connect(cluster['i-001'])
131+
132+
-- The isolated mode is applied, the instance goes to RO.
133+
assert_isolated(true)
134+
assert_read_only(true)
135+
136+
-- Restart i-001, reconnect the console.
137+
g.it:close()
138+
cluster['i-001']:restart()
139+
g.it = it.connect(cluster['i-001'])
140+
141+
-- Still in the isolated mode and RO.
142+
assert_isolated(true)
143+
assert_read_only(true)
144+
145+
-- Disable the isolated mode on i-001.
146+
local config_3 = cbuilder:new(config_2)
147+
:set_instance_option('i-001', 'isolated', nil)
148+
:config()
149+
cluster:reload(config_3)
150+
151+
-- Goes to RW.
152+
assert_isolated(false)
153+
assert_read_only(false)
154+
end

0 commit comments

Comments
 (0)