Skip to content

Commit 48c708b

Browse files
crud: add storage-side API to wait for bootstrap
This patch add a storage handle to wait till module is bootstrapped. The handle is based on existing `storage_info` check. The handle is for using on storages only: it makes no sense to call in on the router. The current standard pattern is as follows: `crud.init_storage()` is called after `box.cfg` on both masters and replicas. In case of Tarantool 1.x and 2.x rw-instances, `crud.init_storage{wait_until_ready = true}` doesn't introduce anything new. In case of Tarantool 1.x and 2.x ro-instances, it helps to wait until persistent part of bootstrap (functions and grants) are replicated. The main motivation behind this handle is the support of asynchronous start that will be introduced in next commits. The asynchronous start is required to properly work with Tarantool 3 instances: all Tarantool 3.x instances start in ro mode. This patch doesn't change existing behavior for Cartridge roles and `crud.init_storage()` calls. Part of #412 Part of #415
1 parent f3056ea commit 48c708b

File tree

8 files changed

+106
-10
lines changed

8 files changed

+106
-10
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## Unreleased
99

10+
### Added
11+
* Storage-side API to wait for bootstrap:
12+
`crud.wait_until_storage_ready()` and `crud.init_storage{wait_until_ready = true}` (#412).
13+
1014
### Fixed
1115
* Compatibility with vshard configuration if UUIDs are omitted (#407).
1216
* Compatibility with automatic master discovery in vshard (#409).

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ across the cluster. The storage-side functions have the same access
9090
as a user calling `crud.init_storage()`. Therefore, if `crud` do not have
9191
enough access to modify some space, then you need to give access to the user.
9292

93+
You can call `crud.init_storage{wait_until_ready = true}` or
94+
`crud.wait_until_storage_ready()` on storages to wait till API is bootstrapped.
95+
9396
All VShard routers should call `crud.init_router()` after `vshard.router.cfg()`
9497
(or enable the `crud-router` role for Cartridge) to make `crud` functions
9598
callable via `net.box`. If a user is allowed to execute `crud` functions on

crud.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ end
175175
--
176176
crud.init_storage = storage.init
177177

178+
crud.wait_until_storage_ready = storage.wait_until_ready
179+
178180
crud.stop_storage = storage.stop
179181

180182
return crud

crud/storage.lua

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
local checks = require('checks')
2+
local errors = require('errors')
3+
local clock = require('clock')
4+
local fiber = require('fiber')
5+
16
local dev_checks = require('crud.common.dev_checks')
27
local utils = require('crud.common.utils')
38

@@ -19,6 +24,8 @@ local borders = require('crud.borders')
1924
local readview = require('crud.readview')
2025
local storage_info = require('crud.storage_info')
2126

27+
local StorageInitError = errors.new_class('StorageInitError')
28+
2229
local storage = {}
2330

2431
local function init_local_part(_, name, func)
@@ -77,18 +84,82 @@ local modules_with_storage_api = {
7784
storage_info,
7885
}
7986

80-
function storage.init()
87+
local function get_operation_user()
88+
return utils.get_this_replica_user() or 'guest'
89+
end
90+
91+
function storage.init(opts)
92+
checks({wait_until_ready = '?boolean', timeout = '?number'})
93+
94+
opts = opts or {}
95+
8196
if type(box.cfg) ~= 'table' then
8297
error('box.cfg() must be called first')
8398
end
8499

85100
rawset(_G, utils.STORAGE_NAMESPACE, {})
86101

87-
local user = utils.get_this_replica_user() or 'guest'
102+
local user = get_operation_user()
88103

89104
for _, module in ipairs(modules_with_storage_api) do
90105
init_storage_call(user, module.storage_api)
91106
end
107+
108+
if opts.wait_until_ready then
109+
storage.wait_until_ready{timeout = opts.timeout}
110+
end
111+
end
112+
113+
local function run_as_user(user, func, ...)
114+
local prev_user = box.session.user()
115+
box.session.su(user)
116+
117+
local res_packed = {func(...)}
118+
119+
box.session.su(prev_user)
120+
121+
return unpack(res_packed)
122+
end
123+
124+
local function is_ready_unsafe_check()
125+
local res = box.schema.func.call(storage_info.CRUD_STORAGE_INFO_FUNC_NAME)
126+
127+
assert(res.status == storage_info.status.RUNNING)
128+
end
129+
130+
local function wait_until_ready(timeout)
131+
local is_ready = false
132+
local deadline = clock.monotonic() + timeout
133+
134+
while clock.monotonic() < deadline do
135+
is_ready = pcall(is_ready_unsafe_check)
136+
137+
if is_ready then
138+
break
139+
end
140+
141+
fiber.sleep(timeout / 100)
142+
end
143+
144+
return is_ready
145+
end
146+
147+
local DEFAULT_WAIT_TIMEOUT = 3
148+
149+
function storage.wait_until_ready(opts)
150+
checks({timeout = '?number'})
151+
152+
opts = opts or {}
153+
local timeout = opts.timeout or DEFAULT_WAIT_TIMEOUT
154+
155+
local user = get_operation_user()
156+
local is_ready = run_as_user(user, wait_until_ready, timeout)
157+
158+
if not is_ready then
159+
return nil, StorageInitError:new("Storage is not bootstrapped")
160+
end
161+
162+
return true
92163
end
93164

94165
function storage.stop()

crud/storage_info.lua

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,23 @@ local storage_info = {}
1313
local STORAGE_INFO_FUNC_NAME = 'storage_info_on_storage'
1414
local CRUD_STORAGE_INFO_FUNC_NAME = utils.get_storage_call(STORAGE_INFO_FUNC_NAME)
1515

16+
storage_info.status = {
17+
RUNNING = 'running',
18+
UNINITIALIZED = 'uninitialized',
19+
ERROR = 'error',
20+
}
21+
1622
--- Storage status information.
1723
--
1824
-- @function storage_info_on_storage
1925
--
2026
-- @return a table with storage status.
2127
local function storage_info_on_storage()
22-
return {status = "running"}
28+
return {status = storage_info.status.RUNNING}
2329
end
2430

2531
storage_info.storage_api = {[STORAGE_INFO_FUNC_NAME] = storage_info_on_storage}
32+
storage_info.CRUD_STORAGE_INFO_FUNC_NAME = CRUD_STORAGE_INFO_FUNC_NAME
2633

2734
--- Polls replicas for storage state
2835
--
@@ -63,7 +70,7 @@ function storage_info.call(opts)
6370
local master = utils.get_replicaset_master(replicaset, {cached = false})
6471

6572
replica_state_by_id[replica_id] = {
66-
status = "error",
73+
status = storage_info.status.ERROR,
6774
is_master = master == replica
6875
}
6976

@@ -97,7 +104,7 @@ function storage_info.call(opts)
97104
local err_msg = string.format("Error getting storage info for %s", replica_id)
98105
if err ~= nil then
99106
if err.type == 'ClientError' and err.code == box.error.NO_SUCH_PROC then
100-
replica_state_by_id[replica_id].status = "uninitialized"
107+
replica_state_by_id[replica_id].status = storage_info.status.UNINITIALIZED
101108
else
102109
log.error("%s: %s", err_msg, err)
103110
replica_state_by_id[replica_id].message = tostring(err)
@@ -107,7 +114,7 @@ function storage_info.call(opts)
107114
replica_state_by_id[replica_id].message = err_msg
108115
end
109116
else
110-
replica_state_by_id[replica_id].status = result[1].status or "uninitialized"
117+
replica_state_by_id[replica_id].status = result[1].status or storage_info.status.UNINITIALIZED
111118
end
112119
end
113120

doc/playground.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ box.once('developers', function()
146146
end)
147147

148148
-- Initialize crud.
149-
crud.init_storage()
149+
crud.init_storage{wait_until_ready = true}
150150
crud.init_router()
151151

152152
-- Start a console.

test/integration/select_readview_test.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2318,7 +2318,7 @@ pgroup.test_stop_select = function(g)
23182318

23192319
g.cluster:server('s2-master'):exec(function(cfg, bootstrap_key)
23202320
require('vshard.storage').cfg(cfg, box.info[bootstrap_key])
2321-
require('crud').init_storage()
2321+
require('crud').init_storage{wait_until_ready = true}
23222322
end, {g.cfg, bootstrap_key})
23232323
end
23242324

test/vshard_helpers/vtest.lua

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,9 +544,18 @@ local function cluster_new(g, cfg)
544544
require(all_init)()
545545
end, {all_init})
546546
end
547-
if crud_init then
547+
end
548+
549+
if crud_init then
550+
for _, replica in pairs(all_servers) do
551+
replica:exec(function()
552+
require('crud').init_storage{wait_until_ready = false}
553+
end)
554+
end
555+
556+
for _, replica in pairs(all_servers) do
548557
replica:exec(function()
549-
require('crud').init_storage()
558+
require('crud').wait_until_storage_ready()
550559
end)
551560
end
552561
end

0 commit comments

Comments
 (0)