New upstream version 0.04
ChangZhuo Chen (陳昌倬)
6 years ago
0 | *.t linguist-language=lua |
0 | modules = { | |
1 | ["resty.redis.connector"] = "lib/resty/redis/connector.lua", | |
2 | ["resty.redis.sentinel"] = "lib/resty/redis/sentinel.lua", | |
3 | } |
0 | 0 | SHELL := /bin/bash # Cheat by using bash :) |
1 | 1 | |
2 | OPENRESTY_PREFIX = /usr/local/openresty-debug | |
2 | OPENRESTY_PREFIX = /usr/local/openresty | |
3 | 3 | |
4 | 4 | TEST_FILE ?= t |
5 | 5 | SENTINEL_TEST_FILE ?= $(TEST_FILE)/sentinel |
20 | 20 | REDIS_SLAVE_ARG := --slaveof 127.0.0.1 $(REDIS_FIRST_PORT) |
21 | 21 | REDIS_CLI := redis-cli -p $(REDIS_FIRST_PORT) -n $(TEST_REDIS_DATABASE) |
22 | 22 | |
23 | # Override socket for running make test on its own | |
23 | # Override socket for running make test on its own | |
24 | 24 | # (make test TEST_REDIS_SOCKET=/path/to/sock.sock) |
25 | 25 | TEST_REDIS_SOCKET ?= $(REDIS_PREFIX)$(REDIS_FIRST_PORT)$(REDIS_SOCK) |
26 | 26 | |
94 | 94 | ) true |
95 | 95 | |
96 | 96 | |
97 | stop_redis_instances: delete_sentinel_config | |
97 | stop_redis_instances: delete_sentinel_config | |
98 | 98 | -@$(foreach port,$(TEST_REDIS_PORTS) $(TEST_SENTINEL_PORTS), \ |
99 | 99 | $(MAKE) stop_redis_instance cleanup_redis_instance port=$(port) \ |
100 | 100 | prefix=$(REDIS_PREFIX)$(port) && \ |
140 | 140 | @$(foreach port,$(REDIS_PORTS),! lsof -i :$(port) &&) true 2>&1 > /dev/null |
141 | 141 | |
142 | 142 | test_redis: flush_db |
143 | util/lua-releng | |
144 | @rm -f luacov.stats.out | |
143 | 145 | $(TEST_REDIS_VARS) $(PROVE) $(TEST_FILE) |
144 | util/lua-releng | |
146 | @luacov | |
147 | @tail -7 luacov.report.out | |
145 | 148 | |
146 | 149 | test_leak: flush_db |
147 | 150 | $(TEST_REDIS_VARS) TEST_NGINX_CHECK_LEAK=1 $(PROVE) $(TEST_FILE) |
0 | 0 | # lua-resty-redis-connector |
1 | 1 | |
2 | Connection utilities for [lua-resty-redis](https://github.com/openresty/lua-resty-redis), making | |
3 | it easy and reliable to connect to Redis hosts, either directly or via | |
4 | [Redis Sentinel](http://redis.io/topics/sentinel). | |
2 | Connection utilities for [lua-resty-redis](https://github.com/openresty/lua-resty-redis), making it easy and reliable to connect to Redis hosts, either directly or via [Redis Sentinel](http://redis.io/topics/sentinel). | |
5 | 3 | |
6 | 4 | |
7 | 5 | ## Synopsis |
8 | 6 | |
9 | ```lua | |
10 | local redis_connector = require "resty.redis.connector" | |
11 | local rc = redis_connector.new() | |
12 | ||
13 | local redis, err = rc:connect{ url = "redis://PASSWORD@127.0.0.1:6379/2" } | |
14 | ||
15 | -- or... | |
16 | ||
17 | local redis, err = rc:connect{ | |
7 | Quick and simple authenticated connection on localhost to DB 2: | |
8 | ||
9 | ```lua | |
10 | local redis, err = require("resty.redis.connector").new({ | |
11 | url = "redis://PASSWORD@127.0.0.1:6379/2", | |
12 | }):connect() | |
13 | ``` | |
14 | ||
15 | More verbose configuration, with timeouts and a default password: | |
16 | ||
17 | ```lua | |
18 | local rc = require("resty.redis.connector").new({ | |
19 | connect_timeout = 50, | |
20 | read_timeout = 5000, | |
21 | keepalive_timeout = 30000, | |
22 | password = "mypass", | |
23 | }) | |
24 | ||
25 | local redis, err = rc:connect({ | |
26 | url = "redis://127.0.0.1:6379/2", | |
27 | }) | |
28 | ||
29 | -- ... | |
30 | ||
31 | local ok, err = rc:set_keepalive(redis) -- uses keepalive params | |
32 | ``` | |
33 | ||
34 | Keep all config in a table, to easily create / close connections as needed: | |
35 | ||
36 | ```lua | |
37 | local rc = require("resty.redis.connector").new({ | |
38 | connect_timeout = 50, | |
39 | read_timeout = 5000, | |
40 | keepalive_timeout = 30000, | |
41 | ||
18 | 42 | host = "127.0.0.1", |
19 | 43 | port = 6379, |
20 | 44 | db = 2, |
21 | password = "PASSWORD", | |
22 | } | |
23 | ||
24 | if not redis then | |
25 | ngx.log(ngx.ERR, err) | |
26 | end | |
27 | ``` | |
45 | password = "mypass", | |
46 | }) | |
47 | ||
48 | local redis, err = rc:connect() | |
49 | ||
50 | -- ... | |
51 | ||
52 | local ok, err = rc:set_keepalive(redis) | |
53 | ``` | |
54 | ||
55 | `connect` can be used to override defaults given in `new` | |
56 | ||
57 | ||
58 | ```lua | |
59 | local rc = require("resty.redis.connector").new({ | |
60 | host = "127.0.0.1", | |
61 | port = 6379, | |
62 | db = 2, | |
63 | }) | |
64 | ||
65 | local redis, err = rc:connect({ | |
66 | db = 5, | |
67 | }) | |
68 | ``` | |
69 | ||
28 | 70 | |
29 | 71 | ## DSN format |
30 | 72 | |
31 | The [connect](#connect) method accepts a single table of named arguments. If the `url` field is | |
32 | present then it will be parsed, overriding values supplied in the parameters table. | |
73 | If the `params.url` field is present then it will be parsed, overriding values supplied in the parameters table. | |
74 | ||
75 | ### Direct Redis connections | |
33 | 76 | |
34 | 77 | The format for connecting directly to Redis is: |
35 | 78 | |
36 | 79 | `redis://PASSWORD@HOST:PORT/DB` |
37 | 80 | |
38 | 81 | The `PASSWORD` and `DB` fields are optional, all other components are required. |
82 | ||
83 | ### Connections via Redis Sentinel | |
39 | 84 | |
40 | 85 | When connecting via Redis Sentinel, the format is as follows: |
41 | 86 | |
47 | 92 | * `s`: slave |
48 | 93 | * `a`: any (first tries the master, but will failover to a slave if required) |
49 | 94 | |
50 | ||
51 | ## Parameters | |
52 | ||
53 | The [connect](#connect) method expects the following field values, either by falling back to | |
54 | defaults, populating the fields by parsing the DSN, or being specified directly. | |
55 | ||
56 | The defaults are as follows: | |
57 | ||
58 | ||
59 | ```lua | |
60 | { | |
61 | host = "127.0.0.1", | |
62 | port = "6379", | |
63 | path = nil, -- unix socket path, e.g. /tmp/redis.sock | |
64 | password = "", | |
65 | db = 0, | |
66 | master_name = "mymaster", | |
67 | role = "master", -- master | slave | any | |
68 | sentinels = nil, | |
69 | } | |
70 | ``` | |
71 | ||
72 | Note that if `sentinel://` is supplied as the `url` parameter, a table of `sentinels` must also | |
73 | be supplied. e.g. | |
95 | A table of `sentinels` must also be supplied. e.g. | |
74 | 96 | |
75 | 97 | ```lua |
76 | 98 | local redis, err = rc:connect{ |
82 | 104 | ``` |
83 | 105 | |
84 | 106 | |
107 | ## Default Parameters | |
108 | ||
109 | ||
110 | ```lua | |
111 | { | |
112 | connect_timeout = 100, | |
113 | read_timeout = 1000, | |
114 | connection_options = {}, -- pool, etc | |
115 | keepalive_timeout = 60000, | |
116 | keepalive_poolsize = 30, | |
117 | ||
118 | host = "127.0.0.1", | |
119 | port = "6379", | |
120 | path = "", -- unix socket path, e.g. /tmp/redis.sock | |
121 | password = "", | |
122 | db = 0, | |
123 | ||
124 | master_name = "mymaster", | |
125 | role = "master", -- master | slave | any | |
126 | sentinels = {}, | |
127 | } | |
128 | ``` | |
129 | ||
130 | ||
85 | 131 | ## API |
86 | 132 | |
87 | 133 | * [new](#new) |
88 | * [set_connect_timeout](#set_connect_timeout) | |
89 | * [set_read_timeout](#set_read_timeout) | |
90 | * [set_connection_options](#set_connection_options) | |
91 | 134 | * [connect](#connect) |
92 | 135 | * [Utilities](#utilities) |
93 | 136 | * [connect_via_sentinel](#connect_via_sentinel) |
94 | 137 | * [try_hosts](#try_hosts) |
95 | 138 | * [connect_to_host](#connect_to_host) |
96 | * [Sentinel Utilities](#sentinel-utilities) | |
97 | 139 | * [sentinel.get_master](#sentinelget_master) |
98 | 140 | * [sentinel.get_slaves](#sentinelget_slaves) |
99 | 141 | |
105 | 147 | Creates the Redis Connector object. In case of failures, returns `nil` and a string describing the error. |
106 | 148 | |
107 | 149 | |
108 | ### set_connect_timeout | |
109 | ||
110 | `syntax: rc:set_connect_timeout(100)` | |
111 | ||
112 | Sets the cosocket connection timeout, in ms. | |
113 | ||
114 | ||
115 | ||
116 | ### set_read_timeout | |
117 | ||
118 | `syntax: rc:set_read_timeout(500)` | |
119 | ||
120 | Sets the cosocket read timeout, in ms. | |
121 | ||
122 | ||
123 | ### set_connection_options | |
124 | ||
125 | `syntax: rc:set_connection_options({ pool = params.host .. ":" .. params.port .. "/" .. params.db })` | |
126 | ||
127 | Sets the connection options table, as supplied to [tcpsock:connect](https://github.com/openresty/lua-nginx-module#tcpsockconnect) | |
128 | method. | |
129 | ||
130 | ||
131 | 150 | ### connect |
132 | 151 | |
133 | 152 | `syntax: redis, err = rc:connect(params)` |
134 | 153 | |
135 | Attempts to create a connection, according to the [params](#parameters) supplied. | |
154 | Attempts to create a connection, according to the [params](#parameters) supplied. If a connection cannot be made, returns `nil` and a string describing the reason. | |
136 | 155 | |
137 | 156 | |
138 | 157 | ## Utilities |
158 | ||
159 | The following methods are not typically needed, but may be useful if a custom interface is required. | |
160 | ||
139 | 161 | |
140 | 162 | ### connect_via_sentinel |
141 | 163 | |
159 | 181 | Attempts to connect to the supplied `host`. |
160 | 182 | |
161 | 183 | |
162 | ## Sentinel Utilities | |
163 | ||
164 | 184 | ### sentinel.get_master |
165 | 185 | |
166 | 186 | `syntax: master, err = sentinel.get_master(sentinel, master_name)` |
175 | 195 | Given a connected Sentinel instance and a master name, will return a list of registered slave Redis instances. |
176 | 196 | |
177 | 197 | |
178 | ## TODO | |
179 | ||
180 | * Redis Cluster support. | |
181 | ||
182 | ||
183 | 198 | # Author |
184 | 199 | |
185 | 200 | James Hurst <james@pintsized.co.uk> |
189 | 204 | |
190 | 205 | This module is licensed under the 2-clause BSD license. |
191 | 206 | |
192 | Copyright (c) 2015, James Hurst <james@pintsized.co.uk> | |
207 | Copyright (c) James Hurst <james@pintsized.co.uk> | |
193 | 208 | |
194 | 209 | All rights reserved. |
195 | 210 |
0 | name=lua-resty-redis-connector | |
1 | abstract=Connection utilities for lua-resty-redis, making it easy and reliable to connect to Redis hosts, either directly or via Redis Sentinel. | |
2 | author=James Hurst | |
3 | is_original=yes | |
4 | license=2bsd | |
5 | lib_dir=lib | |
6 | doc_dir=lib | |
7 | repo_link=https://github.com/pintsized/lua-resty-redis-connector | |
8 | main_module=lib/resty/redis/connector.lua |
0 | local redis = require "resty.redis" | |
1 | redis.add_commands("sentinel") | |
2 | local sentinel = require "resty.redis.sentinel" | |
3 | ||
4 | ||
5 | local ipairs, setmetatable, pcall = ipairs, setmetatable, pcall | |
6 | local ngx_null = ngx.null | |
0 | local ipairs, pcall, error, tostring, type, next, setmetatable, getmetatable = | |
1 | ipairs, pcall, error, tostring, type, next, setmetatable, getmetatable | |
2 | ||
7 | 3 | local ngx_log = ngx.log |
8 | local ngx_DEBUG = ngx.DEBUG | |
9 | 4 | local ngx_ERR = ngx.ERR |
10 | 5 | local ngx_re_match = ngx.re.match |
11 | local tbl_insert = table.insert | |
6 | ||
12 | 7 | local tbl_remove = table.remove |
13 | 8 | local tbl_sort = table.sort |
14 | ||
15 | 9 | local ok, tbl_new = pcall(require, "table.new") |
16 | 10 | if not ok then |
17 | 11 | tbl_new = function (narr, nrec) return {} end |
18 | 12 | end |
19 | 13 | |
20 | ||
21 | local _M = { | |
22 | _VERSION = '0.03', | |
14 | local redis = require("resty.redis") | |
15 | redis.add_commands("sentinel") | |
16 | ||
17 | local get_master = require("resty.redis.sentinel").get_master | |
18 | local get_slaves = require("resty.redis.sentinel").get_slaves | |
19 | ||
20 | ||
21 | -- A metatable which prevents undefined fields from being created / accessed | |
22 | local fixed_field_metatable = { | |
23 | __index = | |
24 | function(t, k) | |
25 | error("field " .. tostring(k) .. " does not exist", 3) | |
26 | end, | |
27 | __newindex = | |
28 | function(t, k, v) | |
29 | error("attempt to create new field " .. tostring(k), 3) | |
30 | end, | |
23 | 31 | } |
24 | 32 | |
25 | local mt = { __index = _M } | |
26 | ||
27 | ||
28 | local DEFAULTS = { | |
33 | ||
34 | -- Returns a new table, recursively copied from the one given, retaining | |
35 | -- metatable assignment. | |
36 | -- | |
37 | -- @param table table to be copied | |
38 | -- @return table | |
39 | local function tbl_copy(orig) | |
40 | local orig_type = type(orig) | |
41 | local copy | |
42 | if orig_type == "table" then | |
43 | copy = {} | |
44 | for orig_key, orig_value in next, orig, nil do | |
45 | copy[tbl_copy(orig_key)] = tbl_copy(orig_value) | |
46 | end | |
47 | setmetatable(copy, tbl_copy(getmetatable(orig))) | |
48 | else -- number, string, boolean, etc | |
49 | copy = orig | |
50 | end | |
51 | return copy | |
52 | end | |
53 | ||
54 | ||
55 | -- Returns a new table, recursively copied from the combination of the given | |
56 | -- table `t1`, with any missing fields copied from `defaults`. | |
57 | -- | |
58 | -- If `defaults` is of type "fixed field" and `t1` contains a field name not | |
59 | -- present in the defults, an error will be thrown. | |
60 | -- | |
61 | -- @param table t1 | |
62 | -- @param table defaults | |
63 | -- @return table a new table, recursively copied and merged | |
64 | local function tbl_copy_merge_defaults(t1, defaults) | |
65 | if t1 == nil then t1 = {} end | |
66 | if defaults == nil then defaults = {} end | |
67 | if type(t1) == "table" and type(defaults) == "table" then | |
68 | local mt = getmetatable(defaults) | |
69 | local copy = {} | |
70 | for t1_key, t1_value in next, t1, nil do | |
71 | copy[tbl_copy(t1_key)] = tbl_copy_merge_defaults( | |
72 | t1_value, tbl_copy(defaults[t1_key]) | |
73 | ) | |
74 | end | |
75 | for defaults_key, defaults_value in next, defaults, nil do | |
76 | if t1[defaults_key] == nil then | |
77 | copy[tbl_copy(defaults_key)] = tbl_copy(defaults_value) | |
78 | end | |
79 | end | |
80 | return copy | |
81 | else | |
82 | return t1 -- not a table | |
83 | end | |
84 | end | |
85 | ||
86 | ||
87 | local DEFAULTS = setmetatable({ | |
88 | connect_timeout = 100, | |
89 | read_timeout = 1000, | |
90 | connection_options = {}, -- pool, etc | |
91 | keepalive_timeout = 60000, | |
92 | keepalive_poolsize = 30, | |
93 | ||
29 | 94 | host = "127.0.0.1", |
30 | 95 | port = 6379, |
31 | path = nil, -- /tmp/redis.sock | |
32 | password = nil, | |
96 | path = "", -- /tmp/redis.sock | |
97 | password = "", | |
33 | 98 | db = 0, |
99 | url = "", -- DSN url | |
100 | ||
34 | 101 | master_name = "mymaster", |
35 | role = "master", -- master | slave | any (tries master first, failover to a slave) | |
36 | sentinels = nil, | |
37 | cluster_startup_nodes = {}, | |
102 | role = "master", -- master | slave | any | |
103 | sentinels = {}, | |
104 | ||
105 | }, fixed_field_metatable) | |
106 | ||
107 | ||
108 | local _M = { | |
109 | _VERSION = '0.04', | |
38 | 110 | } |
39 | 111 | |
40 | ||
41 | function _M.new() | |
42 | return setmetatable({ | |
43 | connect_timeout = 100, | |
44 | read_timeout = 1000, | |
45 | connection_options = nil, -- pool, etc | |
46 | }, mt) | |
47 | end | |
48 | ||
49 | ||
50 | function _M.set_connect_timeout(self, timeout) | |
51 | self.connect_timeout = timeout | |
52 | end | |
53 | ||
54 | ||
55 | function _M.set_read_timeout(self, timeout) | |
56 | self.read_timeout = timeout | |
57 | end | |
58 | ||
59 | ||
60 | function _M.set_connection_options(self, options) | |
61 | self.connection_options = options | |
112 | local mt = { __index = _M } | |
113 | ||
114 | ||
115 | function _M.new(config) | |
116 | local ok, config = pcall(tbl_copy_merge_defaults, config, DEFAULTS) | |
117 | if not ok then | |
118 | return nil, config -- err | |
119 | else | |
120 | return setmetatable({ | |
121 | config = setmetatable(config, fixed_field_metatable) | |
122 | }, mt) | |
123 | end | |
62 | 124 | end |
63 | 125 | |
64 | 126 | |
65 | 127 | local function parse_dsn(params) |
66 | 128 | local url = params.url |
67 | if url then | |
129 | if url and url ~= "" then | |
68 | 130 | local url_pattern = [[^(?:(redis|sentinel)://)(?:([^@]*)@)?([^:/]+)(?::(\d+|[msa]+))/?(.*)$]] |
69 | local m, err = ngx_re_match(url, url_pattern, "") | |
131 | ||
132 | local m, err = ngx_re_match(url, url_pattern, "oj") | |
70 | 133 | if not m then |
71 | ngx_log(ngx_ERR, "could not parse DSN: ", err) | |
72 | else | |
73 | local fields | |
74 | if m[1] == "redis" then | |
75 | fields = { "password", "host", "port", "db" } | |
76 | elseif m[1] == "sentinel" then | |
77 | fields = { "password", "master_name", "role", "db" } | |
134 | return nil, "could not parse DSN: " .. tostring(err) | |
135 | end | |
136 | ||
137 | local fields | |
138 | if m[1] == "redis" then | |
139 | fields = { "password", "host", "port", "db" } | |
140 | elseif m[1] == "sentinel" then | |
141 | fields = { "password", "master_name", "role", "db" } | |
142 | end | |
143 | ||
144 | -- password may not be present | |
145 | if #m < 5 then tbl_remove(fields, 1) end | |
146 | ||
147 | local roles = { m = "master", s = "slave", a = "any" } | |
148 | ||
149 | for i,v in ipairs(fields) do | |
150 | params[v] = m[i + 1] | |
151 | if v == "role" then | |
152 | params[v] = roles[params[v]] | |
78 | 153 | end |
79 | ||
80 | -- password may not be present | |
81 | if #m < 5 then tbl_remove(fields, 1) end | |
82 | ||
83 | local roles = { m = "master", s = "slave", a = "any" } | |
84 | ||
85 | for i,v in ipairs(fields) do | |
86 | params[v] = m[i + 1] | |
87 | if v == "role" then | |
88 | params[v] = roles[params[v]] | |
89 | end | |
90 | end | |
91 | end | |
92 | end | |
93 | end | |
154 | end | |
155 | end | |
156 | ||
157 | return true, nil | |
158 | end | |
159 | _M.parse_dsn = parse_dsn | |
94 | 160 | |
95 | 161 | |
96 | 162 | function _M.connect(self, params) |
97 | -- If we have nothing, assume default host connection options apply | |
98 | if not params or type(params) ~= "table" then | |
99 | params = {} | |
100 | end | |
101 | ||
102 | if params.url then | |
103 | parse_dsn(params) | |
104 | end | |
105 | ||
106 | if params.sentinels then | |
107 | setmetatable(params, { __index = DEFAULTS } ) | |
163 | local params = tbl_copy_merge_defaults(params, self.config) | |
164 | ||
165 | if params.url then | |
166 | local ok, err = parse_dsn(params) | |
167 | if not ok then ngx_log(ngx_ERR, err) end | |
168 | end | |
169 | ||
170 | if #params.sentinels > 0 then | |
108 | 171 | return self:connect_via_sentinel(params) |
109 | elseif params.startup_cluster_nodes then | |
110 | setmetatable(params, { __index = DEFAULTS } ) | |
111 | -- TODO: Implement cluster | |
112 | return nil, "Redis Cluster not yet implemented" | |
113 | else | |
114 | setmetatable(params, { __index = DEFAULTS } ) | |
172 | else | |
115 | 173 | return self:connect_to_host(params) |
116 | 174 | end |
117 | 175 | end |
118 | 176 | |
119 | 177 | |
120 | 178 | local function sort_by_localhost(a, b) |
121 | if a.host == "127.0.0.1" then | |
179 | if a.host == "127.0.0.1" and b.host ~= "127.0.0.1" then | |
122 | 180 | return true |
123 | 181 | else |
124 | 182 | return false |
139 | 197 | end |
140 | 198 | |
141 | 199 | if role == "master" or role == "any" then |
142 | local master, err = sentinel.get_master(sentnl, master_name) | |
200 | local master, err = get_master(sentnl, master_name) | |
143 | 201 | if master then |
144 | 202 | master.db = db |
145 | 203 | master.password = password |
157 | 215 | end |
158 | 216 | |
159 | 217 | -- We either wanted a slave, or are failing over to a slave "any" |
160 | local slaves, err = sentinel.get_slaves(sentnl, master_name) | |
218 | local slaves, err = get_slaves(sentnl, master_name) | |
161 | 219 | sentnl:set_keepalive() |
162 | 220 | |
163 | 221 | if not slaves then |
187 | 245 | -- the last error received, and previous_errors is a table of the previous errors. |
188 | 246 | function _M.try_hosts(self, hosts) |
189 | 247 | local errors = tbl_new(#hosts, 0) |
190 | ||
248 | ||
191 | 249 | for i, host in ipairs(hosts) do |
192 | local r | |
193 | r, errors[i] = self:connect_to_host(host) | |
194 | if r then | |
195 | return r, tbl_remove(errors), errors | |
196 | end | |
197 | end | |
198 | return nil, tbl_remove(errors), errors | |
250 | local r, err = self:connect_to_host(host) | |
251 | if r and not err then | |
252 | return r, nil, errors | |
253 | else | |
254 | errors[i] = err | |
255 | end | |
256 | end | |
257 | ||
258 | return nil, "no hosts available", errors | |
199 | 259 | end |
200 | 260 | |
201 | 261 | |
202 | 262 | function _M.connect_to_host(self, host) |
203 | 263 | local r = redis.new() |
204 | r:set_timeout(self.connect_timeout) | |
264 | local config = self.config | |
265 | r:set_timeout(config.connect_timeout) | |
205 | 266 | |
206 | 267 | local ok, err |
207 | local socket = host.socket | |
208 | if socket then | |
209 | if self.connection_options then | |
210 | ok, err = r:connect(socket, self.connection_options) | |
268 | local path = host.path | |
269 | local opts = config.connection_options | |
270 | if path and path ~= "" then | |
271 | if opts then | |
272 | ok, err = r:connect(path, config.connection_options) | |
211 | 273 | else |
212 | ok, err = r:connect(socket) | |
213 | end | |
214 | else | |
215 | if self.connection_options then | |
216 | ok, err = r:connect(host.host, host.port, self.connection_options) | |
274 | ok, err = r:connect(path) | |
275 | end | |
276 | else | |
277 | if opts then | |
278 | ok, err = r:connect(host.host, host.port, config.connection_options) | |
217 | 279 | else |
218 | 280 | ok, err = r:connect(host.host, host.port) |
219 | 281 | end |
220 | 282 | end |
221 | 283 | |
222 | 284 | if not ok then |
223 | ngx_log(ngx_ERR, err, " for ", host.host, ":", host.port) | |
224 | 285 | return nil, err |
225 | 286 | else |
226 | r:set_timeout(self, self.read_timeout) | |
287 | r:set_timeout(self, config.read_timeout) | |
227 | 288 | |
228 | 289 | local password = host.password |
229 | if password then | |
290 | if password and password ~= "" then | |
230 | 291 | local res, err = r:auth(password) |
231 | 292 | if err then |
232 | 293 | ngx_log(ngx_ERR, err) |
234 | 295 | end |
235 | 296 | end |
236 | 297 | |
237 | r:select(host.db) | |
298 | if host.db ~= nil then | |
299 | r:select(host.db) | |
300 | end | |
238 | 301 | return r, nil |
239 | 302 | end |
240 | 303 | end |
241 | 304 | |
242 | 305 | |
243 | return _M | |
306 | local function set_keepalive(self, redis) | |
307 | -- Restore connection to "NORMAL" before putting into keepalive pool, | |
308 | -- ignoring any errors. | |
309 | redis:discard() | |
310 | ||
311 | local config = self.config | |
312 | return redis:set_keepalive( | |
313 | config.keepalive_timeout, config.keepalive_poolsize | |
314 | ) | |
315 | end | |
316 | _M.set_keepalive = set_keepalive | |
317 | ||
318 | ||
319 | ||
320 | -- Deprecated: use config table in new() or connect() instead. | |
321 | function _M.set_connect_timeout(self, timeout) | |
322 | self.config.connect_timeout = timeout | |
323 | end | |
324 | ||
325 | ||
326 | -- Deprecated: use config table in new() or connect() instead. | |
327 | function _M.set_read_timeout(self, timeout) | |
328 | self.config.read_timeout = timeout | |
329 | end | |
330 | ||
331 | ||
332 | -- Deprecated: use config table in new() or connect() instead. | |
333 | function _M.set_connection_options(self, options) | |
334 | self.config.connection_options = options | |
335 | end | |
336 | ||
337 | ||
338 | return setmetatable(_M, fixed_field_metatable) |
0 | 0 | local ipairs, type = ipairs, type |
1 | ||
1 | 2 | local ngx_null = ngx.null |
3 | ||
2 | 4 | local tbl_insert = table.insert |
3 | ||
4 | 5 | local ok, tbl_new = pcall(require, "table.new") |
5 | 6 | if not ok then |
6 | 7 | tbl_new = function (narr, nrec) return {} end |
7 | 8 | end |
8 | 9 | |
9 | 10 | |
10 | local _M = {} | |
11 | _M._VERSION = 0.03 | |
11 | local _M = { | |
12 | _VERSION = '0.04' | |
13 | } | |
12 | 14 | |
13 | 15 | |
14 | 16 | function _M.get_master(sentinel, master_name) |
0 | package = "lua-resty-redis-connector" | |
1 | version = "0.02-0" | |
2 | source = { | |
3 | url = "git://github.com/pintsized/lua-resty-redis-connector", | |
4 | tag = "v0.02" | |
5 | } | |
6 | description = { | |
7 | summary = "Connection utilities for lua-resty-redis.", | |
8 | detailed = [[ | |
9 | Connection utilities for lua-resty-redis, making it easy and | |
10 | reliable to connect to Redis hosts, either directly or via Redis | |
11 | Sentinel. | |
12 | ]], | |
13 | homepage = "https://github.com/pintsized/lua-resty-redis-connector", | |
14 | license = "2-clause BSD", | |
15 | maintainer = "James Hurst <james@pintsized.co.uk>" | |
16 | } | |
17 | dependencies = { | |
18 | "lua >= 5.1", | |
19 | } | |
20 | build = { | |
21 | type = "builtin", | |
22 | modules = { | |
23 | ["resty.redis.connector"] = "lib/resty/redis/connector.lua", | |
24 | ["resty.redis.sentinel"] = "lib/resty/redis/sentinel.lua" | |
25 | } | |
26 | } |
0 | package = "lua-resty-redis-connector" | |
1 | version = "0.04-0" | |
2 | source = { | |
3 | url = "git://github.com/pintsized/lua-resty-redis-connector", | |
4 | tag = "v0.04" | |
5 | } | |
6 | description = { | |
7 | summary = "Connection utilities for lua-resty-redis.", | |
8 | detailed = [[ | |
9 | Connection utilities for lua-resty-redis, making it easy and | |
10 | reliable to connect to Redis hosts, either directly or via Redis | |
11 | Sentinel. | |
12 | ]], | |
13 | homepage = "https://github.com/pintsized/lua-resty-redis-connector", | |
14 | license = "2-clause BSD", | |
15 | maintainer = "James Hurst <james@pintsized.co.uk>" | |
16 | } | |
17 | dependencies = { | |
18 | "lua >= 5.1", | |
19 | } | |
20 | build = { | |
21 | type = "builtin", | |
22 | modules = { | |
23 | ["resty.redis.connector"] = "lib/resty/redis/connector.lua", | |
24 | ["resty.redis.sentinel"] = "lib/resty/redis/sentinel.lua" | |
25 | } | |
26 | } |
0 | use Test::Nginx::Socket::Lua; | |
1 | use Cwd qw(cwd); | |
2 | ||
3 | repeat_each(2); | |
4 | ||
5 | plan tests => repeat_each() * blocks() * 2; | |
6 | ||
7 | my $pwd = cwd(); | |
8 | ||
9 | our $HttpConfig = qq{ | |
10 | lua_package_path "$pwd/lib/?.lua;;"; | |
11 | ||
12 | init_by_lua_block { | |
13 | require("luacov.runner").init() | |
14 | } | |
15 | }; | |
16 | ||
17 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; | |
18 | ||
19 | no_long_string(); | |
20 | run_tests(); | |
21 | ||
22 | __DATA__ | |
23 | ||
24 | === TEST 1: Default config | |
25 | --- http_config eval: $::HttpConfig | |
26 | --- config | |
27 | location /t { | |
28 | content_by_lua_block { | |
29 | local rc = require("resty.redis.connector").new() | |
30 | ||
31 | local redis = assert(rc:connect(), "rc:connect should return postively") | |
32 | assert(redis:set("foo", "bar"), "redis:set should return positvely") | |
33 | assert(redis:get("foo") == "bar", "get(foo) should return bar") | |
34 | redis:close() | |
35 | } | |
36 | } | |
37 | --- request | |
38 | GET /t | |
39 | --- no_error_log | |
40 | [error] | |
41 | ||
42 | ||
43 | === TEST 2: Defaults via new | |
44 | --- http_config eval: $::HttpConfig | |
45 | --- config | |
46 | location /t { | |
47 | content_by_lua_block { | |
48 | local config = { | |
49 | connect_timeout = 500, | |
50 | port = 6380, | |
51 | db = 6, | |
52 | } | |
53 | local rc = require("resty.redis.connector").new(config) | |
54 | ||
55 | assert(config ~= rc.config, "config should not equal rc.config") | |
56 | assert(rc.config.connect_timeout == 500, "connect_timeout should be 500") | |
57 | assert(rc.config.db == 6, "db should be 6") | |
58 | assert(rc.config.role == "master", "role should be master") | |
59 | } | |
60 | } | |
61 | --- request | |
62 | GET /t | |
63 | --- no_error_log | |
64 | [error] | |
65 | ||
66 | ||
67 | === TEST 3: Config via connect still overrides | |
68 | --- http_config eval: $::HttpConfig | |
69 | --- config | |
70 | location /t { | |
71 | content_by_lua_block { | |
72 | local rc = require("resty.redis.connector").new({ | |
73 | connect_timeout = 500, | |
74 | port = 6380, | |
75 | db = 6, | |
76 | keepalive_poolsize = 10, | |
77 | }) | |
78 | ||
79 | assert(config ~= rc.config, "config should not equal rc.config") | |
80 | assert(rc.config.connect_timeout == 500, "connect_timeout should be 500") | |
81 | assert(rc.config.db == 6, "db should be 6") | |
82 | assert(rc.config.role == "master", "role should be master") | |
83 | assert(rc.config.keepalive_poolsize == 10, | |
84 | "keepalive_poolsize should be 10") | |
85 | ||
86 | local redis = assert(rc:connect({ port = 6379 }), | |
87 | "rc:connect should return positively") | |
88 | } | |
89 | } | |
90 | --- request | |
91 | GET /t | |
92 | --- no_error_log | |
93 | [error] | |
94 | ||
95 | ||
96 | === TEST 4: Unknown config errors, all config does not error | |
97 | --- http_config eval: $::HttpConfig | |
98 | --- config | |
99 | location /t { | |
100 | content_by_lua_block { | |
101 | local rc, err = require("resty.redis.connector").new({ | |
102 | connect_timeout = 500, | |
103 | port = 6380, | |
104 | db = 6, | |
105 | foo = "bar", | |
106 | }) | |
107 | ||
108 | assert(rc == nil, "rc should be nil") | |
109 | assert(string.find(err, "field foo does not exist"), | |
110 | "err should contain error") | |
111 | ||
112 | -- Provide all options, without errors | |
113 | ||
114 | assert(require("resty.redis.connector").new({ | |
115 | connect_timeout = 100, | |
116 | read_timeout = 1000, | |
117 | connection_options = { pool = "<host>::<port>" }, | |
118 | keepalive_timeout = 60000, | |
119 | keepalive_poolsize = 30, | |
120 | ||
121 | host = "127.0.0.1", | |
122 | port = 6379, | |
123 | path = "", | |
124 | password = "", | |
125 | db = 0, | |
126 | ||
127 | url = "", | |
128 | ||
129 | master_name = "mymaster", | |
130 | role = "master", | |
131 | sentinels = {}, | |
132 | }), "new should return positively") | |
133 | ||
134 | -- Provide all options via connect, without errors | |
135 | ||
136 | local rc = require("resty.redis.connector").new() | |
137 | ||
138 | assert(rc:connect({ | |
139 | connect_timeout = 100, | |
140 | read_timeout = 1000, | |
141 | connection_options = { pool = "<host>::<port>" }, | |
142 | keepalive_timeout = 60000, | |
143 | keepalive_poolsize = 30, | |
144 | ||
145 | host = "127.0.0.1", | |
146 | port = 6379, | |
147 | path = "", | |
148 | password = "", | |
149 | db = 0, | |
150 | ||
151 | url = "", | |
152 | ||
153 | master_name = "mymaster", | |
154 | role = "master", | |
155 | sentinels = {}, | |
156 | }), "rc:connect should return positively") | |
157 | } | |
158 | } | |
159 | --- request | |
160 | GET /t | |
161 | --- no_error_log | |
162 | [error] |
0 | # vim:set ft= ts=4 sw=4 et: | |
1 | ||
2 | use Test::Nginx::Socket::Lua; | |
0 | use Test::Nginx::Socket 'no_plan'; | |
3 | 1 | use Cwd qw(cwd); |
4 | 2 | |
5 | repeat_each(2); | |
6 | ||
7 | plan tests => repeat_each() * (3 * blocks()); | |
8 | ||
9 | 3 | my $pwd = cwd(); |
10 | 4 | |
11 | 5 | our $HttpConfig = qq{ |
12 | lua_package_path "$pwd/lib/?.lua;;"; | |
6 | lua_package_path "$pwd/lib/?.lua;;"; | |
7 | ||
8 | init_by_lua_block { | |
9 | require("luacov.runner").init() | |
10 | } | |
13 | 11 | }; |
14 | 12 | |
15 | 13 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; |
16 | 14 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; |
17 | 15 | |
18 | 16 | no_long_string(); |
19 | #no_diff(); | |
20 | ||
21 | 17 | run_tests(); |
22 | 18 | |
23 | 19 | __DATA__ |
24 | 20 | |
25 | === TEST 1: basic | |
26 | --- http_config eval: $::HttpConfig | |
27 | --- config | |
28 | location /t { | |
29 | content_by_lua ' | |
30 | local redis_connector = require "resty.redis.connector" | |
31 | local rc = redis_connector.new() | |
32 | ||
33 | local params = { host = "127.0.0.1", port = $TEST_NGINX_REDIS_PORT } | |
34 | ||
35 | local redis, err = rc:connect(params) | |
36 | if not redis then | |
37 | ngx.say("failed to connect: ", err) | |
38 | return | |
39 | end | |
40 | ||
41 | local res, err = redis:set("dog", "an animal") | |
42 | if not res then | |
43 | ngx.say("failed to set dog: ", err) | |
44 | return | |
45 | end | |
46 | ||
47 | ngx.say("set dog: ", res) | |
48 | ||
49 | redis:close() | |
50 | '; | |
51 | } | |
52 | --- request | |
53 | GET /t | |
54 | --- response_body | |
55 | set dog: OK | |
56 | --- no_error_log | |
57 | [error] | |
58 | ||
59 | ||
60 | === TEST 2: test we can try a list of hosts, and connect to the first working one | |
61 | --- http_config eval: $::HttpConfig | |
62 | --- config | |
63 | location /t { | |
64 | content_by_lua ' | |
65 | local redis_connector = require "resty.redis.connector" | |
66 | local rc = redis_connector.new() | |
67 | rc:set_connect_timeout(100) | |
68 | ||
69 | local hosts = { | |
70 | { host = "127.0.0.1", port = 1 }, | |
71 | { host = "127.0.0.1", port = 2 }, | |
72 | { host = "127.0.0.1", port = $TEST_NGINX_REDIS_PORT }, | |
73 | } | |
74 | ||
75 | local redis, err, previous_errors = rc:try_hosts(hosts) | |
76 | if not redis then | |
77 | ngx.say("failed to connect: ", err) | |
78 | return | |
79 | end | |
80 | ||
81 | -- Print the failed connection errors | |
82 | ngx.say("connection 1 error: ", err) | |
83 | ||
84 | ngx.say("connection 2 error: ", previous_errors[1]) | |
85 | ||
86 | local res, err = redis:set("dog", "an animal") | |
87 | if not res then | |
88 | ngx.say("failed to set dog: ", err) | |
89 | return | |
90 | end | |
91 | ||
92 | ngx.say("set dog: ", res) | |
93 | ||
94 | ||
95 | redis:close() | |
96 | '; | |
97 | } | |
98 | --- request | |
99 | GET /t | |
100 | --- response_body | |
101 | connection 1 error: connection refused | |
102 | connection 2 error: connection refused | |
103 | set dog: OK | |
21 | === TEST 1: basic connect | |
22 | --- http_config eval: $::HttpConfig | |
23 | --- config | |
24 | location /t { | |
25 | content_by_lua_block { | |
26 | local rc = require("resty.redis.connector").new({ | |
27 | port = $TEST_NGINX_REDIS_PORT | |
28 | }) | |
29 | ||
30 | local redis, err = assert(rc:connect(params), | |
31 | "connect should return positively") | |
32 | ||
33 | assert(redis:set("dog", "an animal"), | |
34 | "redis:set should return positively") | |
35 | ||
36 | redis:close() | |
37 | } | |
38 | } | |
39 | --- request | |
40 | GET /t | |
41 | --- no_error_log | |
42 | [error] | |
43 | ||
44 | ||
45 | === TEST 2: try_hosts | |
46 | --- http_config eval: $::HttpConfig | |
47 | --- config | |
48 | location /t { | |
49 | lua_socket_log_errors off; | |
50 | content_by_lua_block { | |
51 | local rc = require("resty.redis.connector").new({ | |
52 | connect_timeout = 100, | |
53 | }) | |
54 | ||
55 | local hosts = { | |
56 | { host = "127.0.0.1", port = 1 }, | |
57 | { host = "127.0.0.1", port = 2 }, | |
58 | { host = "127.0.0.1", port = $TEST_NGINX_REDIS_PORT }, | |
59 | } | |
60 | ||
61 | local redis, err, previous_errors = rc:try_hosts(hosts) | |
62 | assert(redis and not err, | |
63 | "try_hosts should return a connection and no error") | |
64 | ||
65 | assert(previous_errors[1] == "connection refused", | |
66 | "previous_errors[1] should be 'connection refused'") | |
67 | assert(previous_errors[2] == "connection refused", | |
68 | "previous_errors[2] should be 'connection refused'") | |
69 | ||
70 | assert(redis:set("dog", "an animal"), | |
71 | "redis connection should be working") | |
72 | ||
73 | redis:close() | |
74 | ||
75 | local hosts = { | |
76 | { host = "127.0.0.1", port = 1 }, | |
77 | { host = "127.0.0.1", port = 2 }, | |
78 | } | |
79 | ||
80 | local redis, err, previous_errors = rc:try_hosts(hosts) | |
81 | assert(not redis and err == "no hosts available", | |
82 | "no available hosts should return an error") | |
83 | ||
84 | assert(previous_errors[1] == "connection refused", | |
85 | "previous_errors[1] should be 'connection refused'") | |
86 | assert(previous_errors[2] == "connection refused", | |
87 | "previous_errors[2] should be 'connection refused'") | |
88 | } | |
89 | } | |
90 | --- request | |
91 | GET /t | |
92 | --- no_error_log | |
93 | [error] | |
94 | ||
95 | ||
96 | === TEST 3: connect_to_host | |
97 | --- http_config eval: $::HttpConfig | |
98 | --- config | |
99 | location /t { | |
100 | content_by_lua_block { | |
101 | local rc = require("resty.redis.connector").new() | |
102 | ||
103 | local host = { host = "127.0.0.1", port = $TEST_NGINX_REDIS_PORT } | |
104 | ||
105 | local redis, err = rc:connect_to_host(host) | |
106 | assert(redis and not err, | |
107 | "connect_to_host should return positively") | |
108 | ||
109 | assert(redis:set("dog", "an animal"), | |
110 | "redis connection should be working") | |
111 | ||
112 | redis:close() | |
113 | } | |
114 | } | |
115 | --- request | |
116 | GET /t | |
117 | --- no_error_log | |
118 | [error] | |
119 | ||
120 | ||
121 | === TEST 4: connect_to_host options ignore defaults | |
122 | --- http_config eval: $::HttpConfig | |
123 | --- config | |
124 | location /t { | |
125 | content_by_lua_block { | |
126 | local rc = require("resty.redis.connector").new({ | |
127 | port = $TEST_NGINX_REDIS_PORT, | |
128 | db = 2, | |
129 | }) | |
130 | ||
131 | local redis, err = assert(rc:connect_to_host({ | |
132 | host = "127.0.0.1", | |
133 | db = 1, | |
134 | port = $TEST_NGINX_REDIS_PORT | |
135 | }), "connect_to_host should return positively") | |
136 | ||
137 | assert(redis:set("dog", "an animal") == "OK", | |
138 | "set should return 'OK'") | |
139 | ||
140 | redis:select(2) | |
141 | assert(redis:get("dog") == ngx.null, | |
142 | "dog should not exist in db 2") | |
143 | ||
144 | redis:select(1) | |
145 | assert(redis:get("dog") == "an animal", | |
146 | "dog should be 'an animal' in db 1") | |
147 | ||
148 | redis:close() | |
149 | } | |
150 | } | |
151 | --- request | |
152 | GET /t | |
153 | --- no_error_log | |
154 | [error] | |
155 | ||
156 | ||
157 | === TEST 5: Test set_keepalive method | |
158 | --- http_config eval: $::HttpConfig | |
159 | --- config | |
160 | location /t { | |
161 | lua_socket_log_errors Off; | |
162 | content_by_lua_block { | |
163 | local rc = require("resty.redis.connector").new({ | |
164 | port = $TEST_NGINX_REDIS_PORT, | |
165 | }) | |
166 | ||
167 | local redis = assert(rc:connect(), | |
168 | "rc:connect should return positively") | |
169 | local ok, err = rc:set_keepalive(redis) | |
170 | assert(not err, "set_keepalive error should be nil") | |
171 | ||
172 | local ok, err = redis:set("foo", "bar") | |
173 | assert(not ok, "ok should be nil") | |
174 | assert(string.find(err, "closed"), "error should contain 'closed'") | |
175 | ||
176 | local redis = assert(rc:connect(), "connect should return positively") | |
177 | assert(redis:subscribe("channel"), "subscribe should return positively") | |
178 | ||
179 | local ok, err = rc:set_keepalive(redis) | |
180 | assert(not ok, "ok should be nil") | |
181 | assert(string.find(err, "subscribed state"), | |
182 | "error should contain 'subscribed state'") | |
183 | ||
184 | } | |
185 | } | |
186 | --- request | |
187 | GET /t | |
188 | --- no_error_log | |
189 | [error] | |
190 | ||
191 | ||
192 | === TEST 6: password | |
193 | --- http_config eval: $::HttpConfig | |
194 | --- config | |
195 | location /t { | |
196 | lua_socket_log_errors Off; | |
197 | content_by_lua_block { | |
198 | local rc = require("resty.redis.connector").new({ | |
199 | port = $TEST_NGINX_REDIS_PORT, | |
200 | password = "foo", | |
201 | }) | |
202 | ||
203 | local redis, err = rc:connect() | |
204 | assert(not redis and string.find(err, "ERR Client sent AUTH, but no password is set"), | |
205 | "connect should fail with password error") | |
206 | ||
207 | } | |
208 | } | |
209 | --- request | |
210 | GET /t | |
104 | 211 | --- error_log |
105 | 111: Connection refused | |
106 | ||
107 | ||
108 | === TEST 3: Test connect_to_host directly | |
109 | --- http_config eval: $::HttpConfig | |
110 | --- config | |
111 | location /t { | |
112 | content_by_lua ' | |
113 | local redis_connector = require "resty.redis.connector" | |
114 | local rc = redis_connector.new() | |
115 | ||
116 | local host = { host = "127.0.0.1", port = $TEST_NGINX_REDIS_PORT } | |
117 | ||
118 | local redis, err = rc:connect_to_host(host) | |
119 | if not redis then | |
120 | ngx.say("failed to connect: ", err) | |
121 | return | |
122 | end | |
123 | ||
124 | local res, err = redis:set("dog", "an animal") | |
125 | if not res then | |
126 | ngx.say("failed to set dog: ", err) | |
127 | return | |
128 | end | |
129 | ||
130 | ngx.say("set dog: ", res) | |
131 | ||
132 | redis:close() | |
133 | '; | |
134 | } | |
135 | --- request | |
136 | GET /t | |
137 | --- response_body | |
138 | set dog: OK | |
139 | --- no_error_log | |
140 | [error] | |
141 | ||
142 | ||
143 | === TEST 4: Test connect options override | |
144 | --- http_config eval: $::HttpConfig | |
145 | --- config | |
146 | location /t { | |
147 | content_by_lua ' | |
148 | local redis_connector = require "resty.redis.connector" | |
149 | local rc = redis_connector.new() | |
150 | ||
151 | local host = { | |
152 | host = "127.0.0.1", | |
153 | port = $TEST_NGINX_REDIS_PORT, | |
154 | db = 1, | |
155 | } | |
156 | ||
157 | local redis, err = rc:connect_to_host(host) | |
158 | if not redis then | |
159 | ngx.say("failed to connect: ", err) | |
160 | return | |
161 | end | |
162 | ||
163 | local res, err = redis:set("dog", "an animal") | |
164 | if not res then | |
165 | ngx.say("failed to set dog: ", err) | |
166 | return | |
167 | end | |
168 | ||
169 | ngx.say("set dog: ", res) | |
170 | ||
171 | redis:select(2) | |
172 | ngx.say(redis:get("dog")) | |
173 | ||
174 | redis:select(1) | |
175 | ngx.say(redis:get("dog")) | |
176 | ||
177 | redis:close() | |
178 | '; | |
179 | } | |
180 | --- request | |
181 | GET /t | |
182 | --- response_body | |
183 | set dog: OK | |
184 | null | |
185 | an animal | |
186 | --- no_error_log | |
187 | [error] | |
212 | ERR Client sent AUTH, but no password is set | |
213 | ||
214 | ||
215 | === TEST 7: unix domain socket | |
216 | --- http_config eval: $::HttpConfig | |
217 | --- config | |
218 | location /t { | |
219 | lua_socket_log_errors Off; | |
220 | content_by_lua_block { | |
221 | local redis, err = require("resty.redis.connector").new({ | |
222 | path = "unix://tmp/redis.sock", | |
223 | }):connect() | |
224 | ||
225 | assert(not redis and err == "no such file or directory", | |
226 | "bad domain socket should fail") | |
227 | } | |
228 | } | |
229 | --- request | |
230 | GET /t | |
231 | --- no_error_log | |
232 | [error] | |
233 | ||
234 | ||
235 | === TEST 8: parse_dsn | |
236 | --- http_config eval: $::HttpConfig | |
237 | --- config | |
238 | location /t { | |
239 | lua_socket_log_errors Off; | |
240 | content_by_lua_block { | |
241 | local rc = require("resty.redis.connector") | |
242 | ||
243 | local params = { | |
244 | url = "redis://foo@127.0.0.1:$TEST_NGINX_REDIS_PORT/4" | |
245 | } | |
246 | ||
247 | local ok, err = rc.parse_dsn(params) | |
248 | assert(ok and not err, | |
249 | "url should parse without error: " .. tostring(err)) | |
250 | ||
251 | assert(params.host == "127.0.0.1", "host should be localhost") | |
252 | assert(tonumber(params.port) == $TEST_NGINX_REDIS_PORT, | |
253 | "port should be $TEST_NGINX_REDIS_PORT") | |
254 | assert(tonumber(params.db) == 4, "db should be 4") | |
255 | assert(params.password == "foo", "password should be foo") | |
256 | ||
257 | ||
258 | local params = { | |
259 | url = "sentinel://foo@foomaster:s/2" | |
260 | } | |
261 | ||
262 | local ok, err = rc.parse_dsn(params) | |
263 | assert(ok and not err, | |
264 | "url should parse without error: " .. tostring(err)) | |
265 | ||
266 | assert(params.master_name == "foomaster", "master_name should be foomaster") | |
267 | assert(params.role == "slave", "role should be slave") | |
268 | assert(tonumber(params.db) == 2, "db should be 2") | |
269 | ||
270 | ||
271 | local params = { | |
272 | url = "sentinels:/wrongformat", | |
273 | } | |
274 | ||
275 | local ok, err = rc.parse_dsn(params) | |
276 | assert(not ok and err == "could not parse DSN: nil", | |
277 | "url should fail to parse") | |
278 | } | |
279 | } | |
280 | --- request | |
281 | GET /t | |
282 | --- no_error_log | |
283 | [error] |
0 | # vim:set ft= ts=4 sw=4 et: | |
1 | ||
2 | use Test::Nginx::Socket::Lua; | |
0 | use Test::Nginx::Socket 'no_plan'; | |
3 | 1 | use Cwd qw(cwd); |
4 | 2 | |
5 | #repeat_each(2); | |
6 | ||
7 | plan tests => repeat_each() * (3 * blocks()); | |
8 | ||
9 | 3 | my $pwd = cwd(); |
10 | 4 | |
11 | 5 | our $HttpConfig = qq{ |
12 | lua_package_path "$pwd/lib/?.lua;;"; | |
6 | lua_package_path "$pwd/lib/?.lua;;"; | |
7 | ||
8 | init_by_lua_block { | |
9 | require("luacov.runner").init() | |
10 | } | |
13 | 11 | }; |
14 | 12 | |
15 | 13 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; |
16 | 14 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; |
17 | 15 | |
18 | 16 | no_long_string(); |
19 | #no_diff(); | |
20 | ||
21 | 17 | run_tests(); |
22 | 18 | |
23 | 19 | __DATA__ |
25 | 21 | === TEST 1: Get the master |
26 | 22 | --- http_config eval: $::HttpConfig |
27 | 23 | --- config |
28 | location /t { | |
29 | content_by_lua ' | |
30 | local redis_connector = require "resty.redis.connector" | |
31 | local rc = redis_connector.new() | |
32 | ||
33 | local sentinel, err = rc:connect{ url = "redis://127.0.0.1:6381" } | |
34 | if not sentinel then | |
35 | ngx.say("failed to connect: ", err) | |
36 | return | |
37 | end | |
38 | ||
39 | local redis_sentinel = require "resty.redis.sentinel" | |
40 | ||
41 | local master, err = redis_sentinel.get_master(sentinel, "mymaster") | |
42 | if not master then | |
43 | ngx.say(err) | |
44 | else | |
45 | ngx.say("host: ", master.host) | |
46 | ngx.say("port: ", master.port) | |
47 | end | |
48 | ||
49 | sentinel:close() | |
50 | '; | |
51 | } | |
24 | location /t { | |
25 | content_by_lua_block { | |
26 | local rc = require("resty.redis.connector").new() | |
27 | ||
28 | local sentinel, err = rc:connect{ url = "redis://127.0.0.1:6381" } | |
29 | assert(sentinel and not err, "sentinel should connect without errors") | |
30 | ||
31 | local master, err = require("resty.redis.sentinel").get_master( | |
32 | sentinel, | |
33 | "mymaster" | |
34 | ) | |
35 | ||
36 | assert(master and not err, "get_master should return the master") | |
37 | ||
38 | assert(master.host == "127.0.0.1" and tonumber(master.port) == 6379, | |
39 | "host should be 127.0.0.1 and port should be 6379") | |
40 | ||
41 | sentinel:close() | |
42 | } | |
43 | } | |
44 | --- request | |
45 | GET /t | |
46 | --- no_error_log | |
47 | [error] | |
48 | ||
49 | ||
50 | === TEST 1b: Get the master directly | |
51 | --- http_config eval: $::HttpConfig | |
52 | --- config | |
53 | location /t { | |
54 | content_by_lua_block { | |
55 | local rc = require("resty.redis.connector").new() | |
56 | ||
57 | local master, err = rc:connect({ | |
58 | url = "sentinel://mymaster:m/3", | |
59 | sentinels = { | |
60 | { host = "127.0.0.1", port = 6381 } | |
61 | } | |
62 | }) | |
63 | ||
64 | assert(master and not err, "get_master should return the master") | |
65 | assert(master:set("foo", "bar"), "set should run without error") | |
66 | assert(master:get("foo") == "bar", "get(foo) should return bar") | |
67 | ||
68 | master:close() | |
69 | } | |
70 | } | |
71 | --- request | |
72 | GET /t | |
73 | --- no_error_log | |
74 | [error] | |
75 | ||
76 | ||
77 | === TEST 2: Get slaves | |
78 | --- http_config eval: $::HttpConfig | |
79 | --- config | |
80 | location /t { | |
81 | content_by_lua_block { | |
82 | local rc = require("resty.redis.connector").new() | |
83 | ||
84 | local sentinel, err = rc:connect{ url = "redis://127.0.0.1:6381" } | |
85 | assert(sentinel and not err, "sentinel should connect without error") | |
86 | ||
87 | local slaves, err = require("resty.redis.sentinel").get_slaves( | |
88 | sentinel, | |
89 | "mymaster" | |
90 | ) | |
91 | ||
92 | assert(slaves and not err, "slaves should be returned without error") | |
93 | ||
94 | local slaveports = { ["6378"] = false, ["6380"] = false } | |
95 | ||
96 | for _,slave in ipairs(slaves) do | |
97 | slaveports[tostring(slave.port)] = true | |
98 | end | |
99 | ||
100 | assert(slaveports["6378"] == true and slaveports["6380"] == true, | |
101 | "slaves should both be found") | |
102 | ||
103 | sentinel:close() | |
104 | } | |
105 | } | |
52 | 106 | --- request |
53 | 107 | GET /t |
54 | --- response_body | |
55 | host: 127.0.0.1 | |
56 | port: 6379 | |
57 | --- no_error_log | |
58 | [error] | |
59 | ||
60 | ||
61 | === TEST 2: Get slaves | |
62 | --- http_config eval: $::HttpConfig | |
63 | --- config | |
64 | location /t { | |
65 | content_by_lua ' | |
66 | local redis_connector = require "resty.redis.connector" | |
67 | local rc = redis_connector.new() | |
68 | ||
69 | local sentinel, err = rc:connect{ url = "redis://127.0.0.1:6381" } | |
70 | if not sentinel then | |
71 | ngx.say("failed to connect: ", err) | |
72 | return | |
73 | end | |
74 | ||
75 | local redis_sentinel = require "resty.redis.sentinel" | |
76 | ||
77 | local slaves, err = redis_sentinel.get_slaves(sentinel, "mymaster") | |
78 | if not slaves then | |
79 | ngx.say(err) | |
80 | else | |
81 | -- order is undefined | |
82 | local all = {} | |
83 | for i,slave in ipairs(slaves) do | |
84 | all[i] = tonumber(slave.port) | |
85 | end | |
86 | table.sort(all) | |
87 | for _,p in ipairs(all) do | |
88 | ngx.say(p) | |
89 | end | |
90 | end | |
91 | ||
92 | sentinel:close() | |
93 | '; | |
94 | } | |
95 | --- request | |
96 | GET /t | |
97 | --- response_body | |
98 | 6378 | |
99 | 6380 | |
100 | --- no_error_log | |
101 | [error] | |
108 | --- no_error_log | |
109 | [error] | |
110 | ||
102 | 111 | |
103 | 112 | === TEST 3: Get only healthy slaves |
104 | 113 | --- http_config eval: $::HttpConfig |
105 | 114 | --- config |
106 | location /t { | |
107 | content_by_lua ' | |
108 | ||
109 | local redis = require "resty.redis" | |
110 | local r = redis.new() | |
111 | r:connect("127.0.0.1", 6378) | |
112 | r:slaveof("127.0.0.1", 7000) | |
113 | ||
114 | ngx.sleep(9) | |
115 | ||
116 | local redis_connector = require "resty.redis.connector" | |
117 | local rc = redis_connector.new() | |
118 | ||
119 | local sentinel, err = rc:connect{ url = "redis://127.0.0.1:6381" } | |
120 | if not sentinel then | |
121 | ngx.say("failed to connect: ", err) | |
122 | return | |
123 | end | |
124 | ||
125 | local redis_sentinel = require "resty.redis.sentinel" | |
126 | ||
127 | local slaves, err = redis_sentinel.get_slaves(sentinel, "mymaster") | |
128 | if not slaves then | |
129 | ngx.say(err) | |
130 | else | |
131 | for _,slave in ipairs(slaves) do | |
132 | ngx.say("host: ", slave.host) | |
133 | ngx.say("port: ", slave.port) | |
134 | end | |
135 | end | |
136 | ||
137 | sentinel:close() | |
138 | '; | |
139 | } | |
140 | --- request | |
141 | GET /t | |
115 | location /t { | |
116 | content_by_lua_block { | |
117 | local rc = require("resty.redis.connector").new() | |
118 | ||
119 | local sentinel, err = rc:connect({ url = "redis://127.0.0.1:6381" }) | |
120 | assert(sentinel and not err, "sentinel should connect without error") | |
121 | ||
122 | local slaves, err = require("resty.redis.sentinel").get_slaves( | |
123 | sentinel, | |
124 | "mymaster" | |
125 | ) | |
126 | ||
127 | assert(slaves and not err, "slaves should be returned without error") | |
128 | ||
129 | local slaveports = { ["6378"] = false, ["6380"] = false } | |
130 | ||
131 | for _,slave in ipairs(slaves) do | |
132 | slaveports[tostring(slave.port)] = true | |
133 | end | |
134 | ||
135 | assert(slaveports["6378"] == true and slaveports["6380"] == true, | |
136 | "slaves should both be found") | |
137 | ||
138 | -- connect to one and remove it | |
139 | local r = require("resty.redis.connector").new():connect({ | |
140 | port = 6378, | |
141 | }) | |
142 | r:slaveof("127.0.0.1", 7000) | |
143 | ||
144 | ngx.sleep(9) | |
145 | ||
146 | local slaves, err = require("resty.redis.sentinel").get_slaves( | |
147 | sentinel, | |
148 | "mymaster" | |
149 | ) | |
150 | ||
151 | assert(slaves and not err, "slaves should be returned without error") | |
152 | ||
153 | local slaveports = { ["6378"] = false, ["6380"] = false } | |
154 | ||
155 | for _,slave in ipairs(slaves) do | |
156 | slaveports[tostring(slave.port)] = true | |
157 | end | |
158 | ||
159 | assert(slaveports["6378"] == false and slaveports["6380"] == true, | |
160 | "only 6380 should be found") | |
161 | ||
162 | r:slaveof("127.0.0.1", 6379) | |
163 | ||
164 | sentinel:close() | |
165 | } | |
166 | } | |
167 | --- request | |
168 | GET /t | |
142 | 169 | --- timeout: 10 |
143 | --- response_body | |
144 | host: 127.0.0.1 | |
145 | port: 6380 | |
146 | --- no_error_log | |
147 | [error] | |
170 | --- no_error_log | |
171 | [error] | |
172 | ||
173 | ||
174 | === TEST 4: connector.connect_via_sentinel | |
175 | --- http_config eval: $::HttpConfig | |
176 | --- config | |
177 | location /t { | |
178 | content_by_lua_block { | |
179 | local rc = require("resty.redis.connector").new() | |
180 | ||
181 | local params = { | |
182 | sentinels = { | |
183 | { host = "127.0.0.1", port = 6381 }, | |
184 | { host = "127.0.0.1", port = 6382 }, | |
185 | { host = "127.0.0.1", port = 6383 }, | |
186 | }, | |
187 | master_name = "mymaster", | |
188 | role = "master", | |
189 | } | |
190 | ||
191 | local redis, err = rc:connect_via_sentinel(params) | |
192 | assert(redis and not err, "redis should connect without error") | |
193 | ||
194 | params.role = "slave" | |
195 | ||
196 | local redis, err = rc:connect_via_sentinel(params) | |
197 | assert(redis and not err, "redis should connect without error") | |
198 | } | |
199 | } | |
200 | --- request | |
201 | GET /t | |
202 | --- no_error_log | |
203 | [error] | |
204 | ||
205 | ||
206 | === TEST 5: regression for slave sorting (iss12) | |
207 | --- http_config eval: $::HttpConfig | |
208 | --- config | |
209 | location /t { | |
210 | lua_socket_log_errors Off; | |
211 | content_by_lua_block { | |
212 | local rc = require("resty.redis.connector").new() | |
213 | ||
214 | local params = { | |
215 | sentinels = { | |
216 | { host = "127.0.0.1", port = 6381 }, | |
217 | { host = "127.0.0.1", port = 6382 }, | |
218 | { host = "127.0.0.1", port = 6383 }, | |
219 | }, | |
220 | master_name = "mymaster", | |
221 | role = "slave", | |
222 | } | |
223 | ||
224 | -- hotwire get_slaves to expose sorting issue | |
225 | local sentinel = require("resty.redis.sentinel") | |
226 | sentinel.get_slaves = function() | |
227 | return { | |
228 | { host = "127.0.0.1", port = 6380 }, | |
229 | { host = "127.0.0.1", port = 6378 }, | |
230 | { host = "127.0.0.1", port = 6377 }, | |
231 | { host = "134.123.51.2", port = 6380 }, | |
232 | } | |
233 | end | |
234 | ||
235 | local redis, err = rc:connect_via_sentinel(params) | |
236 | assert(redis and not err, "redis should connect without error") | |
237 | } | |
238 | } | |
239 | --- request | |
240 | GET /t | |
241 | --- no_error_log | |
242 | [error] |
38 | 38 | } |
39 | 39 | close $in; |
40 | 40 | |
41 | print "Checking use of Lua global variables in file $file ...\n"; | |
42 | system("luac -p -l $file | grep ETGLOBAL | grep -vE 'require|type|tostring|error|ngx|ndk|jit|setmetatable|getmetatable|string|table|io|os|print|tonumber|math|pcall|xpcall|unpack|pairs|ipairs|assert|module|package|coroutine|[gs]etfenv|next|select|rawset|rawget|debug'"); | |
41 | #print "Checking use of Lua global variables in file $file ...\n"; | |
42 | system("luac5.1 -p -l $file | grep ETGLOBAL | grep -vE '(require|type|tostring|error|ngx|ndk|jit|setmetatable|getmetatable|string|table|io|os|print|tonumber|math|pcall|xpcall|unpack|pairs|ipairs|assert|module|package|coroutine|[gs]etfenv|next|select|rawset|rawget|debug)\$'"); | |
43 | 43 | #file_contains($file, "attempt to write to undeclared variable"); |
44 | 44 | system("grep -H -n -E --color '.{120}' $file"); |
45 | 45 | } |