1#!/usr/bin/env tarantool
2
3local console = require('console')
4local clock = require('clock')
5local log = require('log')
6
7local CONSOLE_PORT = 3302
8
9box.cfg({})
10console.listen(CONSOLE_PORT)
11
12-- forward declarations
13local clean
14local init
15
16-- {{{ locking
17
18local LOCK_LIFETIME = 30 -- seconds
19
20local locked_by = nil
21local locked_at = 0 -- unix time, seconds
22
23local function clean_lock()
24    locked_by = nil
25    locked_at = 0
26    clean()
27    init()
28end
29
30local function set_lock(who)
31    locked_by = who
32    locked_at = clock.monotonic()
33end
34
35local function clean_dead_lock()
36    if locked_by ~= nil and clock.monotonic() - locked_at > LOCK_LIFETIME then
37        log.info(('removed dead "%s" lock'):format(tostring(locked_by)))
38        clean_lock()
39    end
40end
41
42local function is_locked_by(who)
43    return locked_by == who
44end
45
46local function is_locked()
47    return locked_by ~= nil
48end
49
50local function acquire_lock(who)
51    assert(type(who) == 'string')
52    clean_dead_lock()
53    if is_locked_by(who) then
54        -- update lock time
55        set_lock(who)
56        log.info(('updated "%s" lock'):format(who))
57        return true
58    end
59    if is_locked() then
60        local err = 'locked by ' .. tostring(locked_by)
61        log.info(('can not update "%s" lock: %s'):format(who, err))
62        return false, err
63    end
64    set_lock(who)
65    log.info(('set "%s" lock'):format(who))
66    return true
67end
68
69local function touch_lock(who)
70    assert(type(who) == 'string')
71    clean_dead_lock()
72    if is_locked_by(who) then
73        -- update lock time
74        set_lock(who)
75        log.info(('updated "%s" lock'):format(who))
76        return true
77    end
78    if is_locked() then
79        local err = 'locked by ' .. tostring(locked_by)
80        log.info(('can not update "%s" lock: %s'):format(who, err))
81        return false, err
82    end
83    local err = 'is not locked'
84    log.info(('can not update "%s" lock: %s'):format(who, err))
85    return false, err
86end
87
88local function release_lock(who)
89    assert(type(who) == 'string')
90    if is_locked_by(who) then
91        clean_lock()
92        log.info(('released "%s" lock'):format(who))
93        return true
94    end
95    clean_dead_lock()
96    if is_locked() then
97        local err = 'locked by ' .. tostring(locked_by)
98        log.info(('can not release "%s" lock: %s'):format(who, err))
99        return false, err
100    end
101    local err = 'is not locked'
102    log.info(('can not release "%s" lock: %s'):format(who, err))
103    return false, err
104end
105
106-- }}}
107
108-- {{{ init
109
110init = function()
111    _G.acquire_lock = acquire_lock
112    _G.touch_lock = touch_lock
113    _G.release_lock = release_lock
114end
115
116-- }}}
117
118-- {{{ clean
119
120-- Copy of cleanup_cluster() from test_run.lua.
121local function cleanup_cluster()
122    local cluster = box.space._cluster:select()
123    for _, tuple in pairs(cluster) do
124        if tuple[1] ~= box.info.id then
125            box.space._cluster:delete(tuple[1])
126        end
127    end
128end
129
130-- Copy of clean() from pretest_clean.lua from test-run.
131clean = function()
132    local _SPACE_NAME = 3
133
134    box.space._space:pairs():map(function(tuple)
135        local name = tuple[_SPACE_NAME]
136        return name
137    end):filter(function(name)
138        -- skip internal spaces
139        local first_char = string.sub(name, 1, 1)
140        return first_char ~= '_'
141    end):each(function(name)
142        box.space[name]:drop()
143    end)
144
145    local _USER_TYPE = 4
146    local _USER_NAME = 3
147
148    local allowed_users = {
149        guest = true,
150        admin = true,
151    }
152    box.space._user:pairs():filter(function(tuple)
153        local tuple_type = tuple[_USER_TYPE]
154        return tuple_type == 'user'
155    end):map(function(tuple)
156        local name = tuple[_USER_NAME]
157        return name
158    end):filter(function(name)
159        return not allowed_users[name]
160    end):each(function(name)
161        box.schema.user.drop(name)
162    end)
163
164    local allowed_roles = {
165        public = true,
166        replication = true,
167        super = true,
168    }
169    box.space._user:pairs():filter(function(tuple)
170        local tuple_type = tuple[_USER_TYPE]
171        return tuple_type == 'role'
172    end):map(function(tuple)
173        local name = tuple[_USER_NAME]
174        return name
175    end):filter(function(name)
176        return not allowed_roles[name]
177    end):each(function(name)
178        box.schema.role.drop(name)
179    end)
180
181    local _FUNC_NAME = 3
182    local allowed_funcs = {
183        ['box.schema.user.info'] = true,
184    }
185    box.space._func:pairs():map(function(tuple)
186        local name = tuple[_FUNC_NAME]
187        return name
188    end):filter(function(name)
189        return not allowed_funcs[name]
190    end):each(function(name)
191        box.schema.func.drop(name)
192    end)
193
194    cleanup_cluster()
195
196    local cleanup_list = function(list, allowed)
197        for k, _ in pairs(list) do
198            if not allowed[k] then
199                list[k] = nil
200            end
201        end
202    end
203
204    local allowed_globals = {
205        -- modules
206        bit = true,
207        coroutine = true,
208        debug = true,
209        io = true,
210        jit = true,
211        math = true,
212        os = true,
213        package = true,
214        string = true,
215        table = true,
216        utf8 = true,
217        -- variables
218        _G = true,
219        _VERSION = true,
220        arg = true,
221        -- functions
222        assert = true,
223        collectgarbage = true,
224        dofile = true,
225        error = true,
226        gcinfo = true,
227        getfenv = true,
228        getmetatable = true,
229        ipairs = true,
230        load = true,
231        loadfile = true,
232        loadstring = true,
233        module = true,
234        next = true,
235        pairs = true,
236        pcall = true,
237        print = true,
238        rawequal = true,
239        rawget = true,
240        rawset = true,
241        require = true,
242        select = true,
243        setfenv = true,
244        setmetatable = true,
245        tonumber = true,
246        tonumber64 = true,
247        tostring = true,
248        type = true,
249        unpack = true,
250        xpcall = true,
251        -- tarantool
252        _TARANTOOL = true,
253        box = true,
254        dostring = true,
255        help = true,
256        newproxy = true,
257        role_check_grant_revoke_of_sys_priv = true,
258        tutorial = true,
259        update_format = true,
260    }
261    cleanup_list(_G, allowed_globals)
262
263    local allowed_packages = {
264        ['_G'] = true,
265        bit = true,
266        box = true,
267        ['box.backup'] = true,
268        ['box.internal'] = true,
269        ['box.internal.sequence'] = true,
270        ['box.internal.session'] = true,
271        ['box.internal.space'] = true,
272        buffer = true,
273        clock = true,
274        console = true,
275        coroutine = true,
276        crypto = true,
277        csv = true,
278        debug = true,
279        digest = true,
280        errno = true,
281        ffi = true,
282        fiber = true,
283        fio = true,
284        fun = true,
285        help = true,
286        ['help.en_US'] = true,
287        ['http.client'] = true,
288        iconv = true,
289        ['internal.argparse'] = true,
290        ['internal.trigger'] = true,
291        io = true,
292        jit = true,
293        ['jit.bc'] = true,
294        ['jit.bcsave'] = true,
295        ['jit.dis_x64'] = true,
296        ['jit.dis_x86'] = true,
297        ['jit.dump'] = true,
298        ['jit.opt'] = true,
299        ['jit.p'] = true,
300        ['jit.profile'] = true,
301        ['jit.util'] = true,
302        ['jit.v'] = true,
303        ['jit.vmdef'] = true,
304        ['jit.zone'] = true,
305        json = true,
306        log = true,
307        math = true,
308        msgpack = true,
309        msgpackffi = true,
310        ['net.box'] = true,
311        ['net.box.lib'] = true,
312        os = true,
313        package = true,
314        pickle = true,
315        pwd = true,
316        socket = true,
317        strict = true,
318        string = true,
319        table = true,
320        ['table.clear'] = true,
321        ['table.new'] = true,
322        tap = true,
323        tarantool = true,
324        title = true,
325        uri = true,
326        utf8 = true,
327        uuid = true,
328        xlog = true,
329        yaml = true,
330    }
331    cleanup_list(package.loaded, allowed_packages)
332
333    local user_count = box.space._user:count()
334    assert(user_count == 4 or user_count == 5,
335        'box.space._user:count() should be 4 (1.10) or 5 (2.0)')
336    assert(box.space._func:count() == 1,
337        'box.space._func:count() should be only one')
338    assert(box.space._cluster:count() == 1,
339        'box.space._cluster:count() should be only one')
340
341    box.cfg({listen = box.NULL})
342end
343
344-- }}}
345
346clean()
347init()
348