https://github.com/kubeservice-stack/lua-resty-zookeeper
lua-resty-zookeeper for openresty
https://github.com/kubeservice-stack/lua-resty-zookeeper
Last synced: about 1 month ago
JSON representation
lua-resty-zookeeper for openresty
- Host: GitHub
- URL: https://github.com/kubeservice-stack/lua-resty-zookeeper
- Owner: kubeservice-stack
- License: bsd-3-clause
- Created: 2025-12-15T09:50:32.000Z (2 months ago)
- Default Branch: master
- Last Pushed: 2025-12-25T12:01:44.000Z (about 2 months ago)
- Last Synced: 2025-12-26T15:56:34.215Z (about 2 months ago)
- Language: Lua
- Size: 107 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# lua-resty-zookeeper
A minimal, synchronous ZooKeeper client for OpenResty / plain Lua with robust CONNECT handshake parsing and basic operations.
This repository provides:
- `lua/resty/zookeeper.lua` — core client implementation (handshake, signed error handling, create/get_children, exists, get_data, add_auth, close).
- `test/test_zk.lua` — extended test script (connect, exists, get_data, create, get_children, delete, watch).
Notable updates
- Robust CONNECT handshake parsing with heuristics to handle different server response layouts.
- Converts server 32-bit error codes to signed int32 for correct interpretation (e.g. ZNONODE = -101, ZNODEEXISTS = -110).
- Added API:
- `client:create(path, data, mode, sequential)` — create nodes (supports persistent / ephemeral and sequential flags).
- `client:get_children(path)` — list children (returns a Lua table).
- `client:delete(path, version)` — delete a node (supports specifying version; default -1 means any version).
- `client:watch(path, kind)` — register a watcher and block until the next watcher event for this session (synchronous/blocking).
## Quick start (plain Lua)
1. Optional dependency for JSON support:
Using luarocks:
```
luarocks install lua-cjson
```
2. Ensure the project `lua/` directory is on `package.path`.
3. Run the test script:
- Linux / macOS (bash):
```
export LUA_PATH="./lua/?.lua;./lua/?/init.lua;;"
lua test/test_zk.lua 127.0.0.1:2181 /your_parent_path
```
- Windows (PowerShell):
```
$env:LUA_PATH = ".\lua\?.lua;.\lua\?\init.lua;;"
lua test\test_zk.lua 127.0.0.1:2181 \your_parent_path
```
## Quick start (OpenResty)
1. Add the `lua/` directory to `lua_package_path` in `nginx.conf`:
```nginx
lua_package_path "/path/to/project/lua/?.lua;/path/to/project/lua/?/init.lua;;";
```
2. Use in OpenResty Lua code:
```lua
local zk = require("resty.zookeeper")
local client, err = zk.new{
connect_string = "127.0.0.1:2181",
timeout = 3000,
session_timeout = 30000,
debug = false,
}
local ok, err = client:connect()
```
## API summary
- Create client
```lua
local zk = require("zk_connection") -- or require("resty.zookeeper") or require("zookeeper")
local client, err = zk.new{
connect_string = "127.0.0.1:2181",
timeout = 5000, -- ms for ngx, best-effort otherwise
session_timeout = 30000,
debug = true, -- print handshake payload hex when true
}
```
- Connect
```lua
local ok, err = client:connect()
if not ok then error(err) end
```
- Basic operations
```lua
-- exists
local exists, err = client:exists("/myapp")
-- get_data
local data, err = client:get_data("/myapp")
-- add auth (instance method)
local ok, err = client:add_auth("digest", "user:pass")
```
- create
```lua
-- create(path, data, mode, sequential)
-- mode: "persistent" (default) or "ephemeral"
-- sequential: boolean; if true the server appends a monotonically increasing sequence number
local created_path, err = client:create("/myapp/node", "payload", "persistent", true)
```
- get_children
```lua
local children, err = client:get_children("/myapp")
-- returns a Lua table, e.g. { "node0000000001", "node0000000002", ... }
```
- delete
```lua
-- delete(path, version)
-- version: signed int32; default -1 means match any version
local ok, err = client:delete("/myapp/node", -1)
if not ok then
print("delete failed:", err)
end
```
- watch
```lua
-- watch(path, kind)
-- kind: "exists" (default), "get_data", or "get_children"
-- Blocks until a watcher event is delivered for this session.
local evt, err = client:watch("/myapp", "get_children")
if not evt then
print("watch failed:", err)
else
-- evt = { type = , state = , path = "" }
print("watch event:", evt.type, evt.state, evt.path)
end
```
## Error codes
- Server error codes are 32-bit signed integers. This implementation converts received error codes to signed int32 values and interprets common constants:
- -101 (ZNONODE) : node does not exist
- -110 (ZNODEEXISTS) : node already exists
- -103 (ZBADVERSION) : version conflict on update/delete
- Client methods return friendly errors for these conditions (for example, `exists` returns `false, nil` when the node does not exist).
## Testing
- The test script `test/test_zk.lua` demonstrates:
- connecting to a server
- checking for and creating a parent node if necessary
- creating a sequential child node
- listing children via `get_children`
- deleting a created node
- registering a watch and waiting for an event
- Run the test with debug enabled to print handshake payload hex:
```
export LUA_PATH="./lua/?.lua;./lua/?/init.lua;;"
lua test/test_zk.lua 127.0.0.1:2181 /daaa
```
## Watcher testing notes
- Watchers in ZooKeeper are session-scoped and the server sends watcher events to the session that registered them.
- This library implements `watch` as a synchronous blocking call that waits for the next watcher event delivered to the session. Because it blocks, use a separate connection or coroutine for watchers if you need to perform other requests concurrently.
- For reliable testing of watchers, use two clients/processes:
- Client A registers the watch (calls `client:watch(...)`).
- Client B performs an operation (create/delete/set) that triggers the watch.
- Client A should then receive the watcher event.
## Implementation notes
- `delete` encodes the provided version as a 32-bit integer. `version = -1` is encoded as `0xFFFFFFFF`, matching ZooKeeper's signed int32 representation for "any version".
- `watch` sends the corresponding request (exists/get_data/get_children) with the watch flag set to 1, then reads packets until a watcher event (xid == -1, unsigned 4294967295) arrives. Non-watcher packets received while waiting are ignored.
- This client is synchronous and designed for single-request-at-a-time usage per connection. If you require concurrent requests and watcher/event dispatching on a single connection, consider adding:
- an internal packet dispatcher that reads incoming packets continuously,
- routing responses by xid to waiting coroutines,
- delivering watcher events to registered callbacks.
## Limitations and future work
- Current limitations:
- synchronous (blocking) I/O model,
- single-request-per-connection assumption,
- simple watcher support (blocking wait for a single event),
- limited operation coverage and stat parsing,
- no automatic reconnection/session recovery logic,
- limited tests and no CI.
- Potential enhancements:
- add an event-driven dispatcher and non-blocking watcher callbacks,
- implement reconnection and session recovery,
- expand the supported ZooKeeper protocol operations (ACLs, multi, get_acl, set_acl, stat parsing),
- add unit tests and CI workflow,
- publish as a LuaRock.
## License
- BSD