Skip to content

Commit 361cca5

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#10796 NO_DOC=tarantool/doc#4632 NO_CHANGELOG=added together with the configuration option
1 parent 5ae77cf commit 361cca5

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
@@ -644,6 +677,10 @@ local function apply(config)
644677
labels = labels or { alias = names.instance_name },
645678
}
646679

680+
-- RO may be enforced by the isolated mode, so we call the
681+
-- function after all the other logic that may set RW.
682+
switch_isolated_mode(configdata, box_cfg)
683+
647684
-- First box.cfg() call.
648685
--
649686
-- Force the read-only mode if:
@@ -653,6 +690,10 @@ local function apply(config)
653690
-- * there is an existing snapshot (otherwise we wouldn't able
654691
-- to assign a bootstrap leader).
655692
--
693+
-- NB: The read-only mode may be enforced due to other reasons
694+
-- (such as enabled isolated mode) that are not specific to
695+
-- the startup flow.
696+
--
656697
-- The reason is that the configured master may be switched
657698
-- while it is starting. In this case it is undesirable to set
658699
-- RW mode if the actual configuration marks the instance as

test/config-luatest/isolated_mode_test.lua

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

0 commit comments

Comments
 (0)