Skip to content

Commit 4b5961f

Browse files
authored
Add the ddl-manager from cartridge as a new role (#59)
1 parent 0b9926a commit 4b5961f

File tree

10 files changed

+725
-7
lines changed

10 files changed

+725
-7
lines changed

.github/workflows/test.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
id: cache-rocks
2525
with:
2626
path: .rocks/
27-
key: cache-rocks-${{ matrix.runs-on }}-01
27+
key: cache-rocks-${{ matrix.runs-on }}-04
2828
-
2929
run: tarantoolctl rocks install luacheck
3030
if: steps.cache-rocks.outputs.cache-hit != 'true'
@@ -33,9 +33,16 @@ jobs:
3333
if: steps.cache-rocks.outputs.cache-hit != 'true'
3434
- run: echo $PWD/.rocks/bin >> $GITHUB_PATH
3535

36+
- run: tarantoolctl rocks list
37+
- run: tarantoolctl rocks install cartridge
38+
env:
39+
CMAKE_DUMMY_WEBUI: true
40+
- run: tarantoolctl rocks remove ddl --force
41+
3642
- run: luacheck .
3743
- run: tarantoolctl rocks make
3844
- run: luatest -v
3945

4046
# Cleanup cached paths
47+
- run: tarantoolctl rocks remove cartridge
4148
- run: tarantoolctl rocks remove ddl

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
.vscode
33
.rocks
44
/build.luarocks
5+
/tmp
56
.idea

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
88

99
### Added
1010

11-
- Use transactional ddl when applying schema
11+
- Use transactional ddl when applying schema.
12+
- Transfer "ddl-manager" role from the cartridge repo.
1213

1314
## [1.3.0] - 2020-12-25
1415

1516
### Added
1617

17-
- Allow custom fields in space format
18+
- Allow custom fields in space format.
1819
- Forbid redundant keys in schema top-level and make `spaces` table
1920
mandatory. So the only valid schema format now is `{spaces = {...}}`.
2021

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ install(
3737
DESTINATION ${TARANTOOL_INSTALL_LUADIR}/
3838
)
3939

40+
install(
41+
FILES ${CMAKE_CURRENT_SOURCE_DIR}/cartridge/roles/ddl-manager.lua
42+
DESTINATION ${TARANTOOL_INSTALL_LUADIR}/cartridge/roles/
43+
)
44+
4045
file(GLOB_RECURSE LUA_FILES
4146
"${CMAKE_CURRENT_SOURCE_DIR}/ddl/*.lua"
4247
)

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ DDL module for Tarantool 1.10+
1717

1818
## API
1919

20-
- ### Set spaces format
20+
### Set spaces format
2121
`ddl.set_schema(schema)`
2222
- If no spaces existed before, create them.
2323
- If a space exists, check the space's format and indexes.
@@ -28,13 +28,13 @@ DDL module for Tarantool 1.10+
2828

2929
Return values: `true` if no error, otherwise return `nil, err`
3030

31-
- ### Check compatibility
31+
### Check compatibility
3232
`ddl.check_schema(schema)`
3333
- Check that a `set_schema()` call will raise no error.
3434

3535
Return values: `true` if no error, otherwise return `nil, err`
3636

37-
- ### Get spaces format
37+
### Get spaces format
3838
`ddl.get_schema()`
3939
- Scan spaces and return the database schema.
4040

cartridge/roles/ddl-manager.lua

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
local log = require('log')
2+
local ddl = require('ddl')
3+
local yaml = require('yaml').new()
4+
local errors = require('errors')
5+
6+
local cartridge = require('cartridge')
7+
local failover = require('cartridge.failover')
8+
9+
local CheckSchemaError = errors.new_class('CheckSchemaError')
10+
11+
yaml.cfg({
12+
encode_use_tostring = true,
13+
encode_load_metatables = false,
14+
decode_save_metatables = false,
15+
})
16+
17+
local _section_name = 'schema.yml'
18+
local _example_schema = [[## Example:
19+
#
20+
# spaces:
21+
# customer:
22+
# engine: memtx
23+
# is_local: false
24+
# temporary: false
25+
# sharding_key: [customer_id]
26+
# format:
27+
# - {name: customer_id, type: unsigned, is_nullable: false}
28+
# - {name: bucket_id, type: unsigned, is_nullable: false}
29+
# - {name: fullname, type: string, is_nullable: false}
30+
# indexes:
31+
# - name: customer_id
32+
# unique: true
33+
# type: TREE
34+
# parts:
35+
# - {path: customer_id, type: unsigned, is_nullable: false}
36+
#
37+
# - name: bucket_id
38+
# unique: false
39+
# type: TREE
40+
# parts:
41+
# - {path: bucket_id, type: unsigned, is_nullable: false}
42+
#
43+
# - name: fullname
44+
# unique: true
45+
# type: TREE
46+
# parts:
47+
# - {path: fullname, type: string, is_nullable: false}
48+
]]
49+
50+
local function apply_config(conf, opts)
51+
if not opts.is_master then
52+
return true
53+
end
54+
55+
local schema_yml = conf[_section_name]
56+
if schema_yml == nil then
57+
return true
58+
end
59+
60+
assert(type(schema_yml) == 'string')
61+
62+
local schema = yaml.decode(schema_yml)
63+
if schema == nil then
64+
return true
65+
end
66+
67+
assert(ddl.set_schema(schema))
68+
return true
69+
end
70+
71+
local function validate_config(conf_new, _)
72+
local schema_yml = conf_new[_section_name]
73+
if schema_yml == nil then
74+
return true
75+
end
76+
77+
local schema = yaml.decode(schema_yml)
78+
if schema == nil then
79+
return true
80+
end
81+
82+
if type(box.cfg) == 'function' then
83+
log.info(
84+
"Schema validation skipped because" ..
85+
" the instance isn't bootstrapped yet"
86+
)
87+
return true
88+
elseif not failover.is_leader() then
89+
log.info(
90+
"Schema validation skipped because" ..
91+
" the instance isn't a leader"
92+
)
93+
return true
94+
end
95+
96+
local ok, err = ddl.check_schema(schema)
97+
if not ok then
98+
return nil, CheckSchemaError:new(err)
99+
end
100+
101+
return true
102+
end
103+
104+
local function init()
105+
rawset(_G, 'ddl', ddl)
106+
end
107+
108+
local function stop()
109+
rawset(_G, 'ddl', nil)
110+
end
111+
112+
--- Get clusterwide schema as a YAML string.
113+
--
114+
-- In case there's no schema set, return a commented out example.
115+
--
116+
-- @function get_clusterwide_schema_yaml
117+
-- @treturn string yaml-encoded schema
118+
local function get_clusterwide_schema_yaml()
119+
local schema_yml = cartridge.config_get_readonly(_section_name)
120+
121+
if schema_yml == nil or schema_yml == '' then
122+
return _example_schema
123+
else
124+
return schema_yml
125+
end
126+
end
127+
128+
--- Get clusterwide schema as a Lua table.
129+
--
130+
-- In case there's no schema set, return empty schema `{spaces = {}}`.
131+
--
132+
-- @function get_clusterwide_schema_lua
133+
-- @treturn table schema
134+
local function get_clusterwide_schema_lua()
135+
local schema_yml = cartridge.config_get_readonly(_section_name)
136+
local schema_lua = schema_yml and yaml.decode(schema_yml)
137+
if schema_lua == nil then
138+
return {spaces = {}}
139+
else
140+
return schema_lua
141+
end
142+
end
143+
144+
--- Apply schema (as a YAML string) on a whole cluster.
145+
--
146+
-- @function set_clusterwide_schema_yaml
147+
-- @tparam string schema_yml
148+
-- @treturn[1] boolean `true`
149+
-- @treturn[2] nil
150+
-- @treturn[2] table Error object
151+
local function set_clusterwide_schema_yaml(schema_yml)
152+
local patch
153+
if schema_yml == nil then
154+
patch = {[_section_name] = box.NULL}
155+
elseif type(schema_yml) ~= 'string' then
156+
local err = string.format(
157+
'Bad argument #1 to set_clusterwide_schema_yaml' ..
158+
' (?string expected, got %s)', type(schema_yml)
159+
)
160+
error(err, 2)
161+
else
162+
patch = {[_section_name] = schema_yml}
163+
end
164+
165+
return cartridge.config_patch_clusterwide(patch)
166+
end
167+
168+
--- Apply schema (as a Lua table) on a whole cluster.
169+
--
170+
-- @function set_clusterwide_schema_lua
171+
-- @tparam string schema_lua
172+
-- @treturn[1] boolean `true`
173+
-- @treturn[2] nil
174+
-- @treturn[2] table Error object
175+
local function set_clusterwide_schema_lua(schema_lua)
176+
local patch
177+
if schema_lua == nil then
178+
patch = {[_section_name] = box.NULL}
179+
elseif type(schema_lua) ~= 'table' then
180+
local err = string.format(
181+
'Bad argument #1 to set_clusterwide_schema_lua' ..
182+
' (?table expected, got %s)', type(schema_lua)
183+
)
184+
error(err, 2)
185+
else
186+
patch = {[_section_name] = yaml.encode(schema_lua)}
187+
end
188+
189+
return cartridge.config_patch_clusterwide(patch)
190+
end
191+
192+
--- Validate schema passed as a YAML string.
193+
--
194+
-- @function check_schema_yaml
195+
-- @tparam string schema_yml
196+
-- @treturn[1] boolean `true`
197+
-- @treturn[2] nil
198+
-- @treturn[2] table Error object
199+
local function check_schema_yaml(schema_yml)
200+
if schema_yml == nil then
201+
return true
202+
elseif type(schema_yml) ~= 'string' then
203+
return nil, CheckSchemaError:new(
204+
'Bad argument #1 to check_schema_yaml' ..
205+
' (?string expected, got %s)', type(schema_yml)
206+
)
207+
end
208+
209+
local ok, schema_lua = pcall(yaml.decode, schema_yml)
210+
if not ok then
211+
return nil, CheckSchemaError:new(
212+
'Invalid YAML: %s', schema_lua
213+
)
214+
end
215+
216+
if schema_lua == nil then
217+
return true
218+
end
219+
220+
if not failover.is_leader() then
221+
return cartridge.rpc_call(
222+
'ddl-manager', 'check_schema_yaml', {schema_yml},
223+
{leader_only = true}
224+
)
225+
end
226+
227+
local ok, err = ddl.check_schema(schema_lua)
228+
if not ok then
229+
return nil, CheckSchemaError:new(err)
230+
end
231+
232+
return true
233+
end
234+
235+
--- Validate schema passed as a Lua table.
236+
--
237+
-- @function check_schema_lua
238+
-- @tparam string schema_lua
239+
-- @treturn[1] boolean `true`
240+
-- @treturn[2] nil
241+
-- @treturn[2] table Error object
242+
local function check_schema_lua(schema_lua)
243+
if schema_lua == nil then
244+
return true
245+
elseif type(schema_lua) ~= 'table' then
246+
return nil, CheckSchemaError:new(
247+
'Bad argument #1 to check_schema_lua' ..
248+
' (?table expected, got %s)', type(schema_lua)
249+
)
250+
end
251+
252+
-- Always perform encode + decode because
253+
-- that's how `set_schema_lua` works
254+
local ok, schema_yml = pcall(yaml.encode, schema_lua)
255+
if not ok then
256+
return nil, CheckSchemaError:new(
257+
'Encoding YAML failed: %s', schema_yml
258+
)
259+
end
260+
261+
return cartridge.rpc_call(
262+
'ddl-manager', 'check_schema_yaml', {schema_yml},
263+
{prefer_local = true, leader_only = true}
264+
)
265+
end
266+
267+
return {
268+
role_name = 'ddl-manager',
269+
permanent = true,
270+
_section_name = _section_name,
271+
_example_schema = _example_schema,
272+
273+
init = init,
274+
stop = stop,
275+
validate_config = validate_config,
276+
apply_config = apply_config,
277+
278+
get_clusterwide_schema_yaml = get_clusterwide_schema_yaml,
279+
get_clusterwide_schema_lua = get_clusterwide_schema_lua,
280+
281+
set_clusterwide_schema_yaml = set_clusterwide_schema_yaml,
282+
set_clusterwide_schema_lua = set_clusterwide_schema_lua,
283+
284+
check_schema_yaml = check_schema_yaml,
285+
check_schema_lua = check_schema_lua,
286+
287+
CheckSchemaError = CheckSchemaError,
288+
}

test/db.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ end)
1010

1111
local function init()
1212
box.cfg{
13-
work_dir = tempdir,
13+
memtx_dir = tempdir,
14+
vinyl_dir = tempdir,
15+
wal_dir = tempdir,
1416
}
1517
end
1618

test/entrypoint/srv_basic.lua

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env tarantool
2+
3+
require('strict').on()
4+
5+
local cartridge = require('cartridge')
6+
local ok, err = cartridge.cfg({
7+
workdir = 'tmp/db',
8+
roles = {
9+
'cartridge.roles.ddl-manager',
10+
}
11+
})
12+
13+
assert(ok, tostring(err))

0 commit comments

Comments
 (0)