New Upstream Release - lua-nginx-redis-connector

Ready changes

Summary

Merged new upstream version: 0.11.0 (was: 0.06).

Resulting package

Built on 2022-03-16T01:33 (took 2m1s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases lua-nginx-redis-connector

Lintian Result

Diff

diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..5f95a10
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: pintsized
diff --git a/.luacheckrc b/.luacheckrc
new file mode 100644
index 0000000..77ab5df
--- /dev/null
+++ b/.luacheckrc
@@ -0,0 +1,2 @@
+std = "ngx_lua"
+redefined = false
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..0c342c6
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,84 @@
+sudo: required
+dist: focal
+
+os: linux
+
+language: c
+
+compiler: gcc
+
+addons:
+  apt:
+    sources:
+      - sourceline: 'ppa:redislabs/redis'
+    packages:
+      - luarocks
+      - lsof
+
+cache:
+  directories:
+    - download-cache
+
+env:
+  global:
+    - JOBS=3
+    - NGX_BUILD_JOBS=$JOBS
+    - LUAJIT_PREFIX=/opt/luajit21
+    - LUAJIT_LIB=$LUAJIT_PREFIX/lib
+    - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1
+    - LUA_INCLUDE_DIR=$LUAJIT_INC
+    - OPENSSL_PREFIX=/opt/ssl
+    - OPENSSL_LIB=$OPENSSL_PREFIX/lib
+    - OPENSSL_INC=$OPENSSL_PREFIX/include
+    - OPENSSL_VER=1.1.1f
+    - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH
+    - TEST_NGINX_SLEEP=0.006
+    - LUACHECK_VER=0.21.1
+  jobs:
+    - NGINX_VERSION=1.19.9
+
+before_install:
+  # we can't update redis in addons.apt.packages as updated package automatically tries to start and immediately fails
+  - echo exit 101 | sudo tee /usr/sbin/policy-rc.d
+  - sudo chmod +x /usr/sbin/policy-rc.d
+  - sudo apt-get install -y redis-server
+  - sudo luarocks install luacov
+  - sudo luarocks install lua-resty-redis
+  - sudo luarocks install luacheck $LUACHECK_VER
+  - luacheck -q .
+
+install:
+  - if [ ! -d download-cache ]; then mkdir download-cache; fi
+  - if [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -O download-cache/openssl-$OPENSSL_VER.tar.gz https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz; fi
+  - sudo apt-get install -qq -y cpanminus axel
+  - sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1)
+  - git clone https://github.com/openresty/openresty.git ../openresty
+  - git clone https://github.com/openresty/nginx-devel-utils.git
+  - git clone https://github.com/openresty/lua-cjson.git
+  - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module
+  - git clone https://github.com/openresty/stream-lua-nginx-module.git ../stream-lua-nginx-module
+  - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core
+  - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache
+  - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module
+  - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx
+  - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git
+
+script:
+  - sudo iptables -A OUTPUT -p tcp --dst 127.0.0.2 --dport 12345 -j DROP
+  - cd luajit2/
+  - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > build.log 2>&1 || (cat build.log && exit 1)
+  - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1)
+  - cd ../lua-cjson && make && sudo PATH=$PATH make install && cd ..
+  - tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz
+  - cd openssl-$OPENSSL_VER/
+  - ./config shared --prefix=$OPENSSL_PREFIX -DPURIFY > build.log 2>&1 || (cat build.log && exit 1)
+  - make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1)
+  - sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1)
+  - cd ..
+  - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH
+  - export NGX_BUILD_CC=$CC
+  - ngx-build $NGINX_VERSION --with-ipv6 --with-http_realip_module --with-http_ssl_module --with-cc-opt="-I$OPENSSL_INC" --with-ld-opt="-L$OPENSSL_LIB -Wl,-rpath,$OPENSSL_LIB" --add-module=../echo-nginx-module --add-module=../lua-nginx-module --add-module=../stream-lua-nginx-module --with-stream --with-stream_ssl_module --with-debug > build.log 2>&1 || (cat build.log && exit 1)
+  - nginx -V
+  - ldd `which nginx`|grep -E 'luajit|ssl|pcre'
+  - mkdir -p tmp
+  - TMP_DIR=$PWD/tmp make test_all
diff --git a/Makefile b/Makefile
index 7cc9819..f2647be 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ SHELL := /bin/bash # Cheat by using bash :)
 OPENRESTY_PREFIX    = /usr/local/openresty
 
 TEST_FILE          ?= t
-SENTINEL_TEST_FILE ?= $(TEST_FILE)/sentinel
+TMP_DIR			   ?= /tmp
 
 REDIS_CMD           = redis-server
 SENTINEL_CMD        = $(REDIS_CMD) --sentinel
@@ -11,49 +11,69 @@ SENTINEL_CMD        = $(REDIS_CMD) --sentinel
 REDIS_SOCK          = /redis.sock
 REDIS_PID           = /redis.pid
 REDIS_LOG           = /redis.log
-REDIS_PREFIX        = /tmp/redis-
+REDIS_PREFIX        = $(TMP_DIR)/redis-
 
 # Overrideable redis test variables
-TEST_REDIS_PORTS              ?= 6379 6380 6378
-TEST_REDIS_DATABASE           ?= 1
-
-REDIS_FIRST_PORT                    := $(firstword $(TEST_REDIS_PORTS))
-REDIS_SLAVE_ARG                     := --slaveof 127.0.0.1 $(REDIS_FIRST_PORT)
-REDIS_CLI                           := redis-cli -p $(REDIS_FIRST_PORT) -n $(TEST_REDIS_DATABASE)
-
-# Override socket for running make test on its own
-# (make test TEST_REDIS_SOCKET=/path/to/sock.sock)
-TEST_REDIS_SOCKET             ?= $(REDIS_PREFIX)$(REDIS_FIRST_PORT)$(REDIS_SOCK)
+TEST_REDIS_PORT              ?= 6380
+TEST_REDIS_PORT_SL1          ?= 6381
+TEST_REDIS_PORT_SL2          ?= 6382
+TEST_REDIS_PORT_AUTH         ?= 6383
+TEST_REDIS_PORTS             ?= $(TEST_REDIS_PORT) $(TEST_REDIS_PORT_SL1) $(TEST_REDIS_PORT_SL2)
+TEST_REDIS_PORTS_ALL         ?= $(TEST_REDIS_PORTS) $(TEST_REDIS_PORT_AUTH)
+TEST_REDIS_DATABASE          ?= 1
+TEST_REDIS_SOCKET            ?= $(REDIS_PREFIX)$(TEST_REDIS_PORT)$(REDIS_SOCK)
+
+REDIS_SLAVE_ARG                     := --slaveof 127.0.0.1 $(TEST_REDIS_PORT)
+REDIS_CLI                           := redis-cli -p $(TEST_REDIS_PORT) -n $(TEST_REDIS_DATABASE)
 
 # Overrideable redis + sentinel test variables
-TEST_SENTINEL_PORTS           ?= 6381 6382 6383
+TEST_SENTINEL_PORT1           ?= 6390
+TEST_SENTINEL_PORT2           ?= 6391
+TEST_SENTINEL_PORT3           ?= 6392
+TEST_SENTINEL_PORT_AUTH       ?= 6393
+TEST_SENTINEL_PORTS           ?= $(TEST_SENTINEL_PORT1) $(TEST_SENTINEL_PORT2) $(TEST_SENTINEL_PORT3)
+TEST_SENTINEL_PORTS_ALL       ?= $(TEST_SENTINEL_PORTS) $(TEST_SENTINEL_PORT_AUTH)
 TEST_SENTINEL_MASTER_NAME     ?= mymaster
 TEST_SENTINEL_PROMOTION_TIME  ?= 20
 
 # Command line arguments for redis tests
 TEST_REDIS_VARS     = PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$(PATH) \
-TEST_REDIS_SOCKET=unix://$(TEST_REDIS_SOCKET) \
-TEST_REDIS_DATABASE=$(TEST_REDIS_DATABASE) \
+TEST_NGINX_REDIS_PORT=$(TEST_REDIS_PORT) \
+TEST_NGINX_REDIS_PORT_SL1=$(TEST_REDIS_PORT_SL1) \
+TEST_NGINX_REDIS_PORT_SL2=$(TEST_REDIS_PORT_SL2) \
+TEST_NGINX_REDIS_PORT_AUTH=$(TEST_REDIS_PORT_AUTH) \
+TEST_NGINX_REDIS_SOCKET=unix:$(TEST_REDIS_SOCKET) \
+TEST_NGINX_REDIS_DATABASE=$(TEST_REDIS_DATABASE) \
 TEST_NGINX_NO_SHUFFLE=1
 
 # Command line arguments for sentinel tests
 TEST_SENTINEL_VARS  = PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$(PATH) \
-TEST_SENTINEL_PORT=$(firstword $(TEST_SENTINEL_PORTS)) \
-TEST_SENTINEL_MASTER_NAME=$(TEST_SENTINEL_MASTER_NAME) \
-TEST_REDIS_DATABASE=$(TEST_REDIS_DATABASE) \
+TEST_NGINX_REDIS_PORT=$(TEST_NGINX_REDIS_PORT) \
+TEST_NGINX_REDIS_PORT_SL1=$(TEST_NGINX_REDIS_PORT_SL1) \
+TEST_NGINX_REDIS_PORT_SL2=$(TEST_NGINX_REDIS_PORT_SL2) \
+TEST_NGINX_SENTINEL_PORT1=$(TEST_NGINX_SENTINEL_PORT1) \
+TEST_NGINX_SENTINEL_PORT2=$(TEST_NGINX_SENTINEL_PORT2) \
+TEST_NGINX_SENTINEL_PORT3=$(TEST_NGINX_SENTINEL_PORT3) \
+TEST_NGINX_SENTINEL_PORT_AUTH=$(TEST_NGINX_SENTINEL_AUTH) \
+TEST_NGINX_SENTINEL_MASTER_NAME=$(TEST_NGINX_SENTINEL_MASTER_NAME) \
+TEST_NGINX_REDIS_DATABASE=$(TEST_NGINX_REDIS_DATABASE) \
 TEST_NGINX_NO_SHUFFLE=1
 
 # Sentinel configuration can only be set by a config file
 define TEST_SENTINEL_CONFIG
-sentinel       monitor $(TEST_SENTINEL_MASTER_NAME) 127.0.0.1 $(REDIS_FIRST_PORT) 2
+sentinel       monitor $(TEST_SENTINEL_MASTER_NAME) 127.0.0.1 $(TEST_REDIS_PORT) 2
 sentinel       down-after-milliseconds $(TEST_SENTINEL_MASTER_NAME) 2000
 sentinel       failover-timeout $(TEST_SENTINEL_MASTER_NAME) 10000
 sentinel       parallel-syncs $(TEST_SENTINEL_MASTER_NAME) 5
 endef
+define TEST_SENTINEL_AUTH_CONFIG
+sentinel       monitor $(TEST_SENTINEL_MASTER_NAME) 127.0.0.1 $(TEST_REDIS_PORT_AUTH) 1
+endef
 
-export TEST_SENTINEL_CONFIG
+export TEST_SENTINEL_CONFIG TEST_SENTINEL_AUTH_CONFIG
 
 SENTINEL_CONFIG_FILE = /tmp/sentinel-test-config
+SENTINEL_AUTH_CONFIG_FILE = /tmp/sentinel-auth-test-config
 
 
 PREFIX          ?= /usr/local
@@ -76,27 +96,41 @@ install: all
 test: test_redis
 test_all: start_redis_instances sleep test_redis stop_redis_instances
 
+check:
+	luacheck lib
+
 sleep:
 	sleep 3
 
 start_redis_instances: check_ports create_sentinel_config
+	$(REDIS_CMD) --version
+
 	@$(foreach port,$(TEST_REDIS_PORTS), \
-		[[ "$(port)" != "$(REDIS_FIRST_PORT)" ]] && \
+		[[ "$(port)" != "$(TEST_REDIS_PORT)" ]] && \
 			SLAVE="$(REDIS_SLAVE_ARG)" || \
 			SLAVE="" && \
 		$(MAKE) start_redis_instance args="$$SLAVE" port=$(port) \
 		prefix=$(REDIS_PREFIX)$(port) && \
 	) true
 
+	$(MAKE) start_redis_instance \
+		args="--user redisuser on '>redisuserpass' '~*' '&*' '+@all'" \
+		port=$(TEST_REDIS_PORT_AUTH) \
+		prefix=$(REDIS_PREFIX)$(TEST_REDIS_PORT_AUTH)
+
 	@$(foreach port,$(TEST_SENTINEL_PORTS), \
 		$(MAKE) start_redis_instance \
 		port=$(port) args="$(SENTINEL_CONFIG_FILE) --sentinel" \
 		prefix=$(REDIS_PREFIX)$(port) && \
 	) true
 
+	$(MAKE) start_redis_instance \
+		args="$(SENTINEL_AUTH_CONFIG_FILE) --sentinel --user sentineluser on '>sentineluserpass' '~*' '&*' '+@all'" \
+		port=$(TEST_SENTINEL_PORT_AUTH) \
+		prefix=$(REDIS_PREFIX)$(TEST_SENTINEL_PORT_AUTH)
 
 stop_redis_instances: delete_sentinel_config
-	-@$(foreach port,$(TEST_REDIS_PORTS) $(TEST_SENTINEL_PORTS), \
+	-@$(foreach port,$(TEST_REDIS_PORTS_ALL) $(TEST_SENTINEL_PORTS_ALL), \
 		$(MAKE) stop_redis_instance cleanup_redis_instance port=$(port) \
 		prefix=$(REDIS_PREFIX)$(port) && \
 	) true 2>&1 > /dev/null
@@ -105,7 +139,7 @@ stop_redis_instances: delete_sentinel_config
 start_redis_instance:
 	-@echo "Starting redis on port $(port) with args: \"$(args)\""
 	-@mkdir -p $(prefix)
-	@$(REDIS_CMD) $(args) \
+	$(REDIS_CMD) $(args) \
 		--pidfile $(prefix)$(REDIS_PID) \
 		--bind 127.0.0.1 --port $(port) \
 		--unixsocket $(prefix)$(REDIS_SOCK) \
@@ -131,14 +165,18 @@ flush_db:
 create_sentinel_config:
 	-@echo "Creating $(SENTINEL_CONFIG_FILE)"
 	@echo "$$TEST_SENTINEL_CONFIG" > $(SENTINEL_CONFIG_FILE)
+	-@echo "Creating $(SENTINEL_AUTH_CONFIG_FILE)"
+	@echo "$$TEST_SENTINEL_AUTH_CONFIG" > $(SENTINEL_AUTH_CONFIG_FILE)
 
 delete_sentinel_config:
 	-@echo "Removing $(SENTINEL_CONFIG_FILE)"
 	@rm -f $(SENTINEL_CONFIG_FILE)
+	-@echo "Removing $(SENTINEL_AUTH_CONFIG_FILE)"
+	@rm -f $(SENTINEL_AUTH_CONFIG_FILE)
 
 check_ports:
-	-@echo "Checking ports $(REDIS_PORTS)"
-	@$(foreach port,$(REDIS_PORTS),! lsof -i :$(port) &&) true 2>&1 > /dev/null
+	-@echo "Checking ports $(TEST_REDIS_PORTS_ALL) $(TEST_SENTINEL_PORTS_ALL)"
+	@$(foreach port,$(TEST_REDIS_PORTS_ALL) $(TEST_SENTINEL_PORTS_ALL),! lsof -i :$(port) &&) true 2>&1 > /dev/null
 
 test_redis: flush_db
 	util/lua-releng
diff --git a/README.md b/README.md
index 02df770..fe2976c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,12 @@
 # lua-resty-redis-connector
 
-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).
+[![Build
+Status](https://travis-ci.org/ledgetech/lua-resty-redis-connector.svg?branch=master)](https://travis-ci.org/ledgetech/lua-resty-redis-connector)
+
+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).
 
 
 ## Synopsis
@@ -18,6 +24,7 @@ More verbose configuration, with timeouts and a default password:
 ```lua
 local rc = require("resty.redis.connector").new({
     connect_timeout = 50,
+    send_timeout = 5000,
     read_timeout = 5000,
     keepalive_timeout = 30000,
     password = "mypass",
@@ -37,6 +44,7 @@ Keep all config in a table, to easily create / close connections as needed:
 ```lua
 local rc = require("resty.redis.connector").new({
     connect_timeout = 50,
+    send_timeout = 5000,
     read_timeout = 5000,
     keepalive_timeout = 30000,
 
@@ -53,7 +61,8 @@ local redis, err = rc:connect()
 local ok, err = rc:set_keepalive(redis)
 ```
 
-[connect](#connect) can be used to override some defaults given in [new](#new), which are pertinent to this connection only.
+[connect](#connect) can be used to override some defaults given in [new](#new),
+which are pertinent to this connection only.
 
 
 ```lua
@@ -71,25 +80,36 @@ local redis, err = rc:connect({
 
 ## DSN format
 
-If the `params.url` field is present then it will be parsed to set the other params. Any manually specified params will override values given in the DSN.
+If the `params.url` field is present then it will be parsed to set the other
+params. Any manually specified params will override values given in the DSN.
 
-*Note: this is a behaviour change as of v0.06. Previously, the DSN values would take precedence.*
+*Note: this is a behaviour change as of v0.06. Previously, the DSN values would
+take precedence.*
 
 ### Direct Redis connections
 
 The format for connecting directly to Redis is:
 
-`redis://PASSWORD@HOST:PORT/DB`
+`redis://USERNAME:PASSWORD@HOST:PORT/DB`
+
+The `USERNAME`, `PASSWORD` and `DB` fields are optional, all other components
+are required.
 
-The `PASSWORD` and `DB` fields are optional, all other components are required.
+Use of username requires Redis 6.0.0 or newer.
 
 ### Connections via Redis Sentinel
 
 When connecting via Redis Sentinel, the format is as follows:
 
-`sentinel://PASSWORD@MASTER_NAME:ROLE/DB`
+`sentinel://USERNAME:PASSWORD@MASTER_NAME:ROLE/DB`
 
-Again, `PASSWORD` and `DB` are optional. `ROLE` must be either `m` or `s` for master / slave respectively.
+Again, `USERNAME`, `PASSWORD` and `DB` are optional. `ROLE` must be either `m`
+or `s` for master / slave respectively.
+
+On versions of Redis newer than 5.0.1, Sentinels can optionally require their
+own password. If enabled, provide this password in the `sentinel_password`
+parameter. On Redis 6.2.0 and newer you can pass username using
+`sentinel_username` parameter.
 
 A table of `sentinels` must also be supplied. e.g.
 
@@ -97,26 +117,33 @@ A table of `sentinels` must also be supplied. e.g.
 local redis, err = rc:connect{
     url = "sentinel://mymaster:a/2",
     sentinels = {
-        { host = "127.0.0.1", port = 26379" },
-    }
+        { host = "127.0.0.1", port = 26379 },
+    },
+    sentinel_username = "default",
+    sentinel_password = "password"
 }
 ```
 
 ## Proxy Mode
 
-Enable the `connection_is_proxied` parameter if connecting to Redis through a proxy service (e.g. Twemproxy).
-These proxies generally only support a limited sub-set of Redis commands, those which do not require state and do not affect multiple keys.
-Databases and transactions are also not supported.
+Enable the `connection_is_proxied` parameter if connecting to Redis through a
+proxy service (e.g. Twemproxy). These proxies generally only support a limited
+sub-set of Redis commands, those which do not require state and do not affect
+multiple keys. Databases and transactions are also not supported.
 
-Proxy mode will disable switching to a DB on connect.
-Unsupported commands (defaults to those not supported by Twemproxy) will return `nil, err` immediately rather than being sent to the proxy, which can result in dropped connections.
+Proxy mode will disable switching to a DB on connect. Unsupported commands
+(defaults to those not supported by Twemproxy) will return `nil, err`
+immediately rather than being sent to the proxy, which can result in dropped
+connections.
 
 `discard` will not be sent when adding connections to the keepalive pool
 
 
 ## Disabled commands
 
-If configured as a table of commands, the command methods will be replaced by a function which immediately returns `nil, err` without forwarding the command to the server
+If configured as a table of commands, the command methods will be replaced by a
+function which immediately returns `nil, err` without forwarding the command to
+the server
 
 ## Default Parameters
 
@@ -124,15 +151,22 @@ If configured as a table of commands, the command methods will be replaced by a
 ```lua
 {
     connect_timeout = 100,
+    send_timeout = 1000,
     read_timeout = 1000,
-    connection_options = {}, -- pool, etc
     keepalive_timeout = 60000,
     keepalive_poolsize = 30,
 
+    -- ssl, ssl_verify, server_name, pool, pool_size, backlog
+    -- see: https://github.com/openresty/lua-resty-redis#connect
+    connection_options = {},
+
     host = "127.0.0.1",
     port = "6379",
     path = "",  -- unix socket path, e.g. /tmp/redis.sock
+    username = "",
     password = "",
+    sentinel_username = "",
+    sentinel_password = "",
     db = 0,
 
     master_name = "mymaster",
@@ -163,16 +197,21 @@ If configured as a table of commands, the command methods will be replaced by a
 
 `syntax: rc = redis_connector.new(params)`
 
-Creates the Redis Connector object, overring default params with the ones given. In case of failures, returns `nil` and a string describing the error.
+Creates the Redis Connector object, overring default params with the ones given.
+In case of failures, returns `nil` and a string describing the error.
 
 
 ### connect
 
 `syntax: redis, err = rc:connect(params)`
 
-Attempts to create a connection, according to the [params](#parameters) supplied, falling back to defaults given in `new` or the predefined defaults. If a connection cannot be made, returns `nil` and a string describing the reason.
+Attempts to create a connection, according to the [params](#parameters)
+supplied, falling back to defaults given in `new` or the predefined defaults. If
+a connection cannot be made, returns `nil` and a string describing the reason.
 
-Note that `params` given here do not change the connector's own configuration, and are only used to alter this particular connection operation. As such, the following parameters have no meaning when given in `connect`.
+Note that `params` given here do not change the connector's own configuration,
+and are only used to alter this particular connection operation. As such, the
+following parameters have no meaning when given in `connect`.
 
 * `keepalive_poolsize`
 * `keepalive_timeout`
@@ -184,24 +223,28 @@ Note that `params` given here do not change the connector's own configuration, a
 
 `syntax: ok, err = rc:set_keepalive(redis)`
 
-Attempts to place the given Redis connection on the keepalive pool, according to timeout and poolsize params given in `new` or the predefined defaults.
+Attempts to place the given Redis connection on the keepalive pool, according to
+timeout and poolsize params given in `new` or the predefined defaults.
 
-This allows an application to release resources without having to keep track of application wide keepalive settings.
+This allows an application to release resources without having to keep track of
+application wide keepalive settings.
 
 Returns `1` or in the case of error, `nil` and a string describing the error.
 
 
 ## Utilities
 
-The following methods are not typically needed, but may be useful if a custom interface is required.
+The following methods are not typically needed, but may be useful if a custom
+interface is required.
 
 
 ### connect_via_sentinel
 
 `syntax: redis, err = rc:connect_via_sentinel(params)`
 
-Returns a Redis connection by first accessing a sentinel as supplied by the `params.sentinels` table,
-and querying this with the `params.master_name` and `params.role`.
+Returns a Redis connection by first accessing a sentinel as supplied by the
+`params.sentinels` table, and querying this with the `params.master_name` and
+`params.role`.
 
 
 ### try_hosts
@@ -222,14 +265,16 @@ Attempts to connect to the supplied `host`.
 
 `syntax: master, err = sentinel.get_master(sentinel, master_name)`
 
-Given a connected Sentinel instance and a master name, will return the current master Redis instance.
+Given a connected Sentinel instance and a master name, will return the current
+master Redis instance.
 
 
 ### sentinel.get_slaves
 
 `syntax: slaves, err = sentinel.get_slaves(sentinel, master_name)`
 
-Given a connected Sentinel instance and a master name, will return a list of registered slave Redis instances.
+Given a connected Sentinel instance and a master name, will return a list of
+registered slave Redis instances.
 
 
 # Author
@@ -245,10 +290,23 @@ Copyright (c) James Hurst <james@pintsized.co.uk>
 
 All rights reserved.
 
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this
+  list of conditions and the following disclaimer in the documentation and/or
+  other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/debian/changelog b/debian/changelog
index 355bbf6..1f0284e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,12 @@
-lua-nginx-redis-connector (0.06-2) UNRELEASED; urgency=medium
+lua-nginx-redis-connector (0.11.0-1) UNRELEASED; urgency=medium
 
+  [ Ondřej Nový ]
   * d/copyright: Use https protocol in Format field
 
- -- Ondřej Nový <onovy@debian.org>  Mon, 01 Oct 2018 10:45:14 +0200
+  [ Debian Janitor ]
+  * New upstream release.
+
+ -- Ondřej Nový <onovy@debian.org>  Wed, 16 Mar 2022 01:31:30 -0000
 
 lua-nginx-redis-connector (0.06-1) unstable; urgency=medium
 
diff --git a/dist.ini b/dist.ini
index d65c7fd..d8c65b9 100644
--- a/dist.ini
+++ b/dist.ini
@@ -5,5 +5,5 @@ is_original=yes
 license=2bsd
 lib_dir=lib
 doc_dir=lib
-repo_link=https://github.com/pintsized/lua-resty-redis-connector
+repo_link=https://github.com/ledgetech/lua-resty-redis-connector
 main_module=lib/resty/redis/connector.lua
diff --git a/lib/resty/redis/connector.lua b/lib/resty/redis/connector.lua
index 00d0f61..ee14807 100644
--- a/lib/resty/redis/connector.lua
+++ b/lib/resty/redis/connector.lua
@@ -5,11 +5,13 @@ local ngx_log = ngx.log
 local ngx_ERR = ngx.ERR
 local ngx_re_match = ngx.re.match
 
+local str_find = string.find
+local str_sub = string.sub
 local tbl_remove = table.remove
 local tbl_sort = table.sort
 local ok, tbl_new = pcall(require, "table.new")
 if not ok then
-    tbl_new = function (narr, nrec) return {} end
+    tbl_new = function (narr, nrec) return {} end -- luacheck: ignore 212
 end
 
 local redis = require("resty.redis")
@@ -22,11 +24,11 @@ local get_slaves = require("resty.redis.sentinel").get_slaves
 -- A metatable which prevents undefined fields from being created / accessed
 local fixed_field_metatable = {
     __index =
-        function(t, k)
+        function(t, k) -- luacheck: ignore 212
             error("field " .. tostring(k) .. " does not exist", 3)
         end,
     __newindex =
-        function(t, k, v)
+        function(t, k, v) -- luacheck: ignore 212
             error("attempt to create new field " .. tostring(k), 3)
         end,
 }
@@ -66,7 +68,6 @@ local function tbl_copy_merge_defaults(t1, defaults)
     if t1 == nil then t1 = {} end
     if defaults == nil then defaults = {} end
     if type(t1) == "table" and type(defaults) == "table" then
-        local mt = getmetatable(defaults)
         local copy = {}
         for t1_key, t1_value in next, t1, nil do
             copy[tbl_copy(t1_key)] = tbl_copy_merge_defaults(
@@ -88,6 +89,7 @@ end
 local DEFAULTS = setmetatable({
     connect_timeout = 100,
     read_timeout = 1000,
+    send_timeout = 1000,
     connection_options = {}, -- pool, etc
     keepalive_timeout = 60000,
     keepalive_poolsize = 30,
@@ -95,7 +97,10 @@ local DEFAULTS = setmetatable({
     host = "127.0.0.1",
     port = 6379,
     path = "", -- /tmp/redis.sock
+    username = "",
     password = "",
+    sentinel_username = "",
+    sentinel_password = "",
     db = 0,
     url = "", -- DSN url
 
@@ -124,7 +129,7 @@ local default_disabled_commands = {
 
 
 local _M = {
-    _VERSION = '0.06',
+    _VERSION = '0.11.0',
 }
 
 local mt = { __index = _M }
@@ -148,33 +153,63 @@ local function parse_dsn(params)
             fields = { "password", "master_name", "role", "db" }
         end
 
-        -- password may not be present
+        -- username/password may not be present
         if #m < 5 then tbl_remove(fields, 1) end
 
         local roles = { m = "master", s = "slave" }
 
         local parsed_params = {}
 
-        for i,v in ipairs(fields) do
-            parsed_params[v] = m[i + 1]
+        for i, v in ipairs(fields) do
+            if v == "db" or v == "port" then
+                parsed_params[v] = tonumber(m[i + 1])
+            else
+                parsed_params[v] = m[i + 1]
+            end
+
             if v == "role" then
                 parsed_params[v] = roles[parsed_params[v]]
             end
         end
 
+        local colon_pos = str_find(parsed_params.password or "", ":", 1, true)
+        if colon_pos then
+            parsed_params.username = str_sub(parsed_params.password, 1, colon_pos - 1)
+            parsed_params.password = str_sub(parsed_params.password, colon_pos + 1)
+        end
+
         return tbl_copy_merge_defaults(params, parsed_params)
     end
+
+    return params
 end
 _M.parse_dsn = parse_dsn
 
 
-function _M.new(config)
-    -- Fill out gaps in config with any dsn params
+-- Fill out gaps in config with any dsn params
+local function apply_dsn(config)
     if config and config.url then
         local err
         config, err = parse_dsn(config)
-        if not ok then ngx_log(ngx_ERR, err) end
+        if err then ngx_log(ngx_ERR, err) end
     end
+    return config
+end
+
+
+-- For backwards compatability; previously send_timeout was implicitly the
+-- same as read_timeout. So if only the latter is given, ensure the former
+-- matches.
+local function apply_fallback_send_timeout(config)
+    if config and not config.send_timeout and config.read_timeout then
+        config.send_timeout = config.read_timeout
+    end
+end
+
+
+function _M.new(config)
+    config = apply_dsn(config)
+    apply_fallback_send_timeout(config)
 
     local ok, config = pcall(tbl_copy_merge_defaults, config, DEFAULTS)
     if not ok then
@@ -195,11 +230,8 @@ end
 
 
 function _M.connect(self, params)
-    if params and params.url then
-        local err
-        params, err = parse_dsn(params)
-        if not ok then ngx_log(ngx_ERR, err) end
-    end
+    params = apply_dsn(params)
+    apply_fallback_send_timeout(params)
 
     params = tbl_copy_merge_defaults(params, self.config)
 
@@ -225,7 +257,16 @@ function _M.connect_via_sentinel(self, params)
     local master_name = params.master_name
     local role = params.role
     local db = params.db
+    local username = params.username
     local password = params.password
+    local sentinel_username = params.sentinel_username
+    local sentinel_password = params.sentinel_password
+    if sentinel_password then
+        for _, host in ipairs(sentinels) do
+            host.username = sentinel_username
+            host.password = sentinel_password
+        end
+    end
 
     local sentnl, err, previous_errors = self:try_hosts(sentinels)
     if not sentnl then
@@ -234,36 +275,38 @@ function _M.connect_via_sentinel(self, params)
 
     if role == "master" then
         local master, err = get_master(sentnl, master_name)
-        if master then
-            master.db = db
-            master.password = password
-
-            local redis, err = self:connect_to_host(master)
-            if redis then
-                sentnl:set_keepalive()
-                return redis, err
-            else
-                if role == "master" then
-                    return nil, err
-                end
-            end
+        if not master then
+            return nil, err
+        end
+
+        sentnl:set_keepalive()
+
+        master.db = db
+        master.username = username
+        master.password = password
+
+        local redis, err = self:connect_to_host(master)
+        if not redis then
+            return nil, err
         end
 
+        return redis
     else
         -- We want a slave
         local slaves, err = get_slaves(sentnl, master_name)
-        sentnl:set_keepalive()
-
         if not slaves then
             return nil, err
         end
 
+        sentnl:set_keepalive()
+
         -- Put any slaves on 127.0.0.1 at the front
         tbl_sort(slaves, sort_by_localhost)
 
         if db or password then
-            for i,slave in ipairs(slaves) do
+            for _, slave in ipairs(slaves) do
                 slave.db = db
+                slave.username = username
                 slave.password = password
             end
         end
@@ -271,9 +314,9 @@ function _M.connect_via_sentinel(self, params)
         local slave, err, previous_errors = self:try_hosts(slaves)
         if not slave then
             return nil, err, previous_errors
-        else
-            return slave
         end
+
+        return slave
     end
 end
 
@@ -298,13 +341,27 @@ end
 
 function _M.connect_to_host(self, host)
     local r = redis.new()
-    local config = self.config
-    r:set_timeout(config.connect_timeout)
+
+    -- config options in 'host' should override the global defaults
+    -- host contains keys that aren't in config
+    -- this can break tbl_copy_merge_defaults, hence the mannual loop here
+    local config = tbl_copy(self.config)
+    for k, _ in pairs(config) do
+        if host[k] then
+            config[k] = host[k]
+        end
+    end
+
+    r:set_timeouts(
+        config.connect_timeout,
+        config.send_timeout,
+        config.read_timeout
+    )
 
     -- Stub out methods for disabled commands
     if next(config.disabled_commands) then
         for _, cmd in ipairs(config.disabled_commands) do
-            r[cmd] = function(...)
+            r[cmd] = function()
                 return nil, ("Command "..cmd.." is disabled")
             end
         end
@@ -330,27 +387,43 @@ function _M.connect_to_host(self, host)
     if not ok then
         return nil, err
     else
-        r:set_timeout(self, config.read_timeout)
-
+        local username = host.username
         local password = host.password
         if password and password ~= "" then
-            local res, err = r:auth(password)
+            local res
+            -- usernames are supported only on Redis 6+, so use new AUTH form only when absolutely necessary
+            if username and username ~= "" and username ~= "default" then
+                res, err = r:auth(username, password)
+            else
+                res, err = r:auth(password)
+            end
             if err then
-                ngx_log(ngx_ERR, err)
                 return res, err
             end
         end
 
         -- No support for DBs in proxied Redis.
         if config.connection_is_proxied ~= true and host.db ~= nil then
-            r:select(host.db)
+            local res, err = r:select(host.db)
+
+            -- SELECT will fail if we are connected to sentinel:
+            -- detect it and ignore error message it that's the case
+            if err and str_find(err, "ERR unknown command") then
+                local role = r:role()
+                if role and role[1] == "sentinel" then
+                    err = nil
+                end
+            end
+            if err then
+                return res, err
+            end
         end
         return r, nil
     end
 end
 
 
-local function set_keepalive(self, redis)
+function _M.set_keepalive(self, redis)
     -- Restore connection to "NORMAL" before putting into keepalive pool,
     -- ignoring any errors.
     -- Proxied Redis does not support transactions.
@@ -363,8 +436,6 @@ local function set_keepalive(self, redis)
         config.keepalive_timeout, config.keepalive_poolsize
     )
 end
-_M.set_keepalive = set_keepalive
-
 
 
 -- Deprecated: use config table in new() or connect() instead.
diff --git a/lib/resty/redis/sentinel.lua b/lib/resty/redis/sentinel.lua
index 41becdb..905fff9 100644
--- a/lib/resty/redis/sentinel.lua
+++ b/lib/resty/redis/sentinel.lua
@@ -5,12 +5,12 @@ local ngx_null = ngx.null
 local tbl_insert = table.insert
 local ok, tbl_new = pcall(require, "table.new")
 if not ok then
-    tbl_new = function (narr, nrec) return {} end
+    tbl_new = function (narr, nrec) return {} end -- luacheck: ignore 212
 end
 
 
 local _M = {
-    _VERSION = '0.06'
+    _VERSION = '0.11.0'
 }
 
 
@@ -21,6 +21,8 @@ function _M.get_master(sentinel, master_name)
     )
     if res and res ~= ngx_null and res[1] and res[2] then
         return { host = res[1], port = res[2] }
+    elseif res == ngx_null then
+        return nil, "invalid master name"
     else
         return nil, err
     end
@@ -39,7 +41,10 @@ function _M.get_slaves(sentinel, master_name)
                 host[slave[i]] = slave[i + 1]
             end
 
-            if host["master-link-status"] == "ok" then
+            local master_link_status_ok = host["master-link-status"] == "ok"
+            local is_down = host["flags"] and (string.find(host["flags"],"s_down")
+                or string.find(host["flags"],"disconnected"))
+            if master_link_status_ok and not is_down then
                 host.host = host.ip -- for parity with other functions
                 tbl_insert(hosts, host)
             end
diff --git a/lua-resty-redis-connector-0.06-0.rockspec b/lua-resty-redis-connector-0.11.0-0.rockspec
similarity index 78%
rename from lua-resty-redis-connector-0.06-0.rockspec
rename to lua-resty-redis-connector-0.11.0-0.rockspec
index f287dd9..b44c1e9 100644
--- a/lua-resty-redis-connector-0.06-0.rockspec
+++ b/lua-resty-redis-connector-0.11.0-0.rockspec
@@ -1,8 +1,8 @@
 package = "lua-resty-redis-connector"
-version = "0.06-0"
+version = "0.11.0-0"
 source = {
-  url = "git://github.com/pintsized/lua-resty-redis-connector",
-  tag = "v0.06"
+  url = "git://github.com/ledgetech/lua-resty-redis-connector",
+  tag = "v0.11.0"
 }
 description = {
   summary = "Connection utilities for lua-resty-redis.",
@@ -11,7 +11,7 @@ description = {
     reliable to connect to Redis hosts, either directly or via Redis
     Sentinel.
   ]],
-  homepage = "https://github.com/pintsized/lua-resty-redis-connector",
+  homepage = "https://github.com/ledgetech/lua-resty-redis-connector",
   license = "2-clause BSD",
   maintainer = "James Hurst <james@pintsized.co.uk>"
 }
diff --git a/t/config.t b/t/config.t
index 34a615d..7b31c60 100644
--- a/t/config.t
+++ b/t/config.t
@@ -9,13 +9,14 @@ my $pwd = cwd();
 
 our $HttpConfig = qq{
 lua_package_path "$pwd/lib/?.lua;;";
+lua_socket_log_errors Off;
 
 init_by_lua_block {
     require("luacov.runner").init()
 }
 };
 
-$ENV{TEST_NGINX_REDIS_PORT} ||= 6379;
+$ENV{TEST_NGINX_REDIS_PORT} ||= 6380;
 
 no_long_string();
 run_tests();
@@ -27,12 +28,7 @@ __DATA__
 --- config
 location /t {
     content_by_lua_block {
-        local rc = require("resty.redis.connector").new()
-
-        local redis = assert(rc:connect(), "rc:connect should return postively")
-        assert(redis:set("foo", "bar"), "redis:set should return positvely")
-        assert(redis:get("foo") == "bar", "get(foo) should return bar")
-        redis:close()
+        local rc = assert(require("resty.redis.connector").new())
     }
 }
 --- request
@@ -48,7 +44,7 @@ location /t {
     content_by_lua_block {
         local config = {
             connect_timeout = 500,
-            port = 6380,
+            port = $TEST_NGINX_REDIS_PORT,
             db = 6,
         }
         local rc = require("resty.redis.connector").new(config)
@@ -72,7 +68,7 @@ location /t {
     content_by_lua_block {
         local rc = require("resty.redis.connector").new({
             connect_timeout = 500,
-            port = 6380,
+            port = $TEST_NGINX_REDIS_PORT,
             db = 6,
             keepalive_poolsize = 10,
         })
@@ -84,8 +80,18 @@ location /t {
         assert(rc.config.keepalive_poolsize == 10,
             "keepalive_poolsize should be 10")
 
-        local redis = assert(rc:connect({ port = 6379 }),
-            "rc:connect should return positively")
+        local redis, err = rc:connect({
+            port = $TEST_NGINX_REDIS_PORT,
+            disabled_commands = { "set" }
+        })
+
+        if not redis or err then
+            ngx.log(ngx.ERR, "connect failed: ", err)
+            return
+        end
+
+        local ok, err = redis:set("foo", "bar")
+            assert( ok == nil and (string.find(err, "disabled") ~= nil) , "Disabled commands not passed through" )
     }
 }
 --- request
@@ -101,7 +107,7 @@ location /t {
     content_by_lua_block {
         local rc, err = require("resty.redis.connector").new({
             connect_timeout = 500,
-            port = 6380,
+            port = $TEST_NGINX_REDIS_PORT,
             db = 6,
             foo = "bar",
         })
@@ -114,14 +120,16 @@ location /t {
 
         assert(require("resty.redis.connector").new({
             connect_timeout = 100,
+            send_timeout = 500,
             read_timeout = 1000,
             connection_options = { pool = "<host>::<port>" },
             keepalive_timeout = 60000,
             keepalive_poolsize = 30,
 
             host = "127.0.0.1",
-            port = 6379,
+            port = $TEST_NGINX_REDIS_PORT,
             path = "",
+            username = "",
             password = "",
             db = 0,
 
@@ -138,14 +146,16 @@ location /t {
 
         assert(rc:connect({
             connect_timeout = 100,
+            send_timeout = 500,
             read_timeout = 1000,
             connection_options = { pool = "<host>::<port>" },
             keepalive_timeout = 60000,
             keepalive_poolsize = 30,
 
             host = "127.0.0.1",
-            port = 6379,
+            port = $TEST_NGINX_REDIS_PORT,
             path = "",
+            username = "",
             password = "",
             db = 0,
 
@@ -161,3 +171,59 @@ location /t {
 GET /t
 --- no_error_log
 [error]
+
+
+=== TEST 5: timeout defaults
+--- http_config eval: $::HttpConfig
+--- config
+location /t {
+    content_by_lua_block {
+        -- global defaults
+        local rc = require("resty.redis.connector").new({
+            port = $TEST_NGINX_REDIS_PORT,
+            db = 6,
+            keepalive_poolsize = 10,
+        })
+
+        assert(rc.config.connect_timeout == 100, "connect_timeout should be 100")
+        assert(rc.config.send_timeout == 1000, "send_timeout should be 1000")
+        assert(rc.config.read_timeout == 1000, "read_timeout should be 1000")
+
+        local redis = assert(rc:connect(), "rc:connect should return positively")
+        assert(redis:set("foo", "bar"))
+        rc:set_keepalive(redis)
+
+        -- send_timeout defaults to read_timeout
+        rc = require("resty.redis.connector").new({
+            read_timeout = 500,
+            port = $TEST_NGINX_REDIS_PORT,
+            db = 6,
+            keepalive_poolsize = 10,
+        })
+
+        assert(rc.config.connect_timeout == 100, "connect_timeout should be 100")
+        assert(rc.config.send_timeout == 500, "send_timeout should be 500")
+        assert(rc.config.read_timeout == 500, "read_timeout should be 500")
+
+        local redis = assert(rc:connect(), "rc:connect should return positively")
+        assert(redis:set("foo", "bar"))
+        rc:set_keepalive(redis)
+
+        -- send_timeout can be set separately from read_timeout
+        rc = require("resty.redis.connector").new({
+            send_timeout = 500,
+            read_timeout = 200,
+            port = $TEST_NGINX_REDIS_PORT,
+            db = 6,
+            keepalive_poolsize = 10,
+        })
+
+        assert(rc.config.connect_timeout == 100, "connect_timeout should be 100")
+        assert(rc.config.send_timeout == 500, "send_timeout should be 500")
+        assert(rc.config.read_timeout == 200, "read_timeout should be 200")
+    }
+}
+--- request
+GET /t
+--- no_error_log
+[error]
diff --git a/t/connector.t b/t/connector.t
index 8a16b03..dbb9eb2 100644
--- a/t/connector.t
+++ b/t/connector.t
@@ -5,6 +5,7 @@ my $pwd = cwd();
 
 our $HttpConfig = qq{
 lua_package_path "$pwd/lib/?.lua;;";
+lua_socket_log_errors Off;
 
 init_by_lua_block {
     require("luacov.runner").init()
@@ -12,7 +13,9 @@ init_by_lua_block {
 };
 
 $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
-$ENV{TEST_NGINX_REDIS_PORT} ||= 6379;
+$ENV{TEST_NGINX_REDIS_PORT} ||= 6380;
+$ENV{TEST_NGINX_REDIS_PORT_AUTH} ||= 6393;
+$ENV{TEST_NGINX_REDIS_SOCKET} ||= 'unix://tmp/redis/redis.sock';
 
 no_long_string();
 run_tests();
@@ -63,10 +66,10 @@ location /t {
         assert(redis and not err,
             "try_hosts should return a connection and no error")
 
-        assert(previous_errors[1] == "connection refused",
-            "previous_errors[1] should be 'connection refused'")
-        assert(previous_errors[2] == "connection refused",
-            "previous_errors[2] should be 'connection refused'")
+        assert(string.len(previous_errors[1]) > 0,
+            "previous_errors[1] should contain an error")
+        assert(string.len(previous_errors[2]) > 0,
+            "previous_errors[2] should contain an error")
 
         assert(redis:set("dog", "an animal"),
             "redis connection should be working")
@@ -82,10 +85,10 @@ location /t {
         assert(not redis and err == "no hosts available",
             "no available hosts should return an error")
 
-        assert(previous_errors[1] == "connection refused",
-            "previous_errors[1] should be 'connection refused'")
-        assert(previous_errors[2] == "connection refused",
-            "previous_errors[2] should be 'connection refused'")
+        assert(string.len(previous_errors[1]) > 0,
+            "previous_errors[1] should contain an error")
+        assert(string.len(previous_errors[2]) > 0,
+            "previous_errors[2] should contain an error")
     }
 }
 --- request
@@ -202,25 +205,47 @@ location /t {
         })
 
         local redis, err = rc:connect()
-        assert(not redis and string.find(err, "ERR Client sent AUTH, but no password is set"),
+        assert(not redis and string.find(err, "ERR") and string.find(err, "AUTH"),
             "connect should fail with password error")
 
     }
 }
 --- request
 GET /t
---- error_log
-ERR Client sent AUTH, but no password is set
+--- no_error_log
+[error]
+
+=== TEST 7: username and password
+--- http_config eval: $::HttpConfig
+--- config
+location /t {
+    lua_socket_log_errors Off;
+    content_by_lua_block {
+        local rc = require("resty.redis.connector").new({
+            port = $TEST_NGINX_REDIS_PORT,
+            username = "x",
+            password = "foo",
+        })
+
+        local redis, err = rc:connect()
+        assert(not redis and string.find(err, "WRONGPASS"),
+            "connect should fail with invalid username-password error")
+    }
+}
+--- request
+GET /t
+--- no_error_log
+[error]
 
 
-=== TEST 7: unix domain socket
+=== TEST 8: Bad unix domain socket path should fail
 --- http_config eval: $::HttpConfig
 --- config
 location /t {
     lua_socket_log_errors Off;
     content_by_lua_block {
         local redis,  err = require("resty.redis.connector").new({
-            path = "unix://tmp/redis.sock",
+            path = "unix://GARBAGE_PATH_AKFDKAJSFKJSAFLKJSADFLKJSANCKAJSNCKJSANCLKAJS",
         }):connect()
 
         assert(not redis and err == "no such file or directory",
@@ -233,7 +258,29 @@ GET /t
 [error]
 
 
-=== TEST 8: parse_dsn
+=== TEST 8.1: Good unix domain socket path should succeed
+--- http_config eval: $::HttpConfig
+--- config
+location /t {
+    lua_socket_log_errors Off;
+    content_by_lua_block {
+        local redis, err = require("resty.redis.connector").new({
+            path = "$TEST_NGINX_REDIS_SOCKET",
+        }):connect()
+
+        assert (redis and not err,
+            "connection should be valid")
+
+        redis:close()
+    }
+}
+--- request
+GET /t
+--- no_error_log
+[error]
+
+
+=== TEST 9: parse_dsn
 --- http_config eval: $::HttpConfig
 --- config
 location /t {
@@ -249,33 +296,35 @@ location /t {
         assert(params and not err,
             "url should parse without error: " .. tostring(err))
 
-		assert(params.host == "127.0.0.1", "host should be localhost")
-		assert(tonumber(params.port) == $TEST_NGINX_REDIS_PORT,
-			"port should be $TEST_NGINX_REDIS_PORT")
-		assert(tonumber(params.db) == 4, "db should be 4")
-		assert(params.password == "foo", "password should be foo")
+        assert(params.host == "127.0.0.1", "host should be localhost")
+        assert(tonumber(params.port) == $TEST_NGINX_REDIS_PORT,
+            "port should be $TEST_NGINX_REDIS_PORT")
+        assert(tonumber(params.db) == 4, "db should be 4")
+        assert(params.password == "foo", "password should be foo")
 
 
-		local user_params = {
-			url = "sentinel://foo@foomaster:s/2"
-		}
+        local user_params = {
+            url = "sentinel://foo:bar@foomaster:s/2"
+        }
 
-		local params, err = rc.parse_dsn(user_params)
-		assert(params and not err,
-			"url should parse without error: " .. tostring(err))
+        local params, err = rc.parse_dsn(user_params)
+        assert(params and not err,
+            "url should parse without error: " .. tostring(err))
 
-		assert(params.master_name == "foomaster", "master_name should be foomaster")
-		assert(params.role == "slave", "role should be slave")
-		assert(tonumber(params.db) == 2, "db should be 2")
+        assert(params.master_name == "foomaster", "master_name should be foomaster")
+        assert(params.role == "slave", "role should be slave")
+        assert(tonumber(params.db) == 2, "db should be 2")
+        assert(params.username == "foo", "username should be foo")
+        assert(params.password == "bar", "password should be bar")
 
 
-		local params = {
-			url = "sentinels:/wrongformat",
-		}
+        local params = {
+            url = "sentinels:/wrongformat",
+        }
 
-		local ok, err = rc.parse_dsn(params)
-		assert(not ok and err == "could not parse DSN: nil",
-			"url should fail to parse")
+        local ok, err = rc.parse_dsn(params)
+        assert(not ok and err == "could not parse DSN: nil",
+            "url should fail to parse")
     }
 }
 --- request
@@ -284,7 +333,7 @@ GET /t
 [error]
 
 
-=== TEST 9: params override dsn components
+=== TEST 10: params override dsn components
 --- http_config eval: $::HttpConfig
 --- config
 location /t {
@@ -293,7 +342,7 @@ location /t {
         local rc = require("resty.redis.connector")
 
         local user_params = {
-            url = "redis://foo@127.0.0.1:6381/4",
+            url = "redis://foo@127.0.0.1:$TEST_NGINX_REDIS_PORT/4",
             db = 2,
             password = "bar",
             host = "example.com",
@@ -307,7 +356,7 @@ location /t {
         assert(params.password == "bar", "password should be bar")
         assert(params.host == "example.com", "host should be example.com")
 
-        assert(tonumber(params.port) == 6381, "ort should still be 6381")
+        assert(tonumber(params.port) == $TEST_NGINX_REDIS_PORT, "port should still be $TEST_NGINX_REDIS_PORT")
 
     }
 }
@@ -317,7 +366,7 @@ GET /t
 [error]
 
 
-=== TEST 9: Integration test for parse_dsn
+=== TEST 11: Integration test for parse_dsn
 --- http_config eval: $::HttpConfig
 --- config
 location /t {
@@ -354,6 +403,37 @@ location /t {
         assert(redis and not err, "connect should return positively")
         assert(redis:set("cat", "dog") and redis:get("cat") == "dog")
 
+        local redis, err = rc2:connect({
+            url = "redis://redisuser:redisuserpass@127.0.0.1:$TEST_NGINX_REDIS_PORT_AUTH/"
+        })
+        assert(redis and not err, "connect should return positively")
+        local username = assert(redis:acl("whoami"))
+        assert(username == "redisuser", "should connect as 'redisuser' but got " .. tostring(username))
+    }
+}
+--- request
+GET /t
+--- no_error_log
+[error]
+
+
+=== TEST 12: DSN without DB
+--- http_config eval: $::HttpConfig
+--- config
+location /t {
+    lua_socket_log_errors Off;
+    content_by_lua_block {
+        local user_params = {
+            url = "redis://foo.example:$TEST_NGINX_REDIS_PORT",
+            host = "127.0.0.1",
+        }
+
+        local rc, err = require("resty.redis.connector").new(user_params)
+        assert(rc and not err, "new should return positively")
+
+        local redis, err = rc:connect()
+        assert(redis and not err, "connect should return positively")
+        assert(redis:set("cat", "dog") and redis:get("cat") == "dog")
     }
 }
 --- request
diff --git a/t/proxy.t b/t/proxy.t
index 6b28eb3..f775a7d 100644
--- a/t/proxy.t
+++ b/t/proxy.t
@@ -5,6 +5,7 @@ my $pwd = cwd();
 
 our $HttpConfig = qq{
 lua_package_path "$pwd/lib/?.lua;;";
+lua_socket_log_errors Off;
 
 init_by_lua_block {
     require("luacov.runner").init()
@@ -12,7 +13,7 @@ init_by_lua_block {
 };
 
 $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
-$ENV{TEST_NGINX_REDIS_PORT} ||= 6379;
+$ENV{TEST_NGINX_REDIS_PORT} ||= 6380;
 
 no_long_string();
 run_tests();
diff --git a/t/sentinel.t b/t/sentinel.t
index 764c883..2b5d2bc 100644
--- a/t/sentinel.t
+++ b/t/sentinel.t
@@ -5,6 +5,7 @@ my $pwd = cwd();
 
 our $HttpConfig = qq{
 lua_package_path "$pwd/lib/?.lua;;";
+lua_socket_log_errors Off;
 
 init_by_lua_block {
     require("luacov.runner").init()
@@ -12,7 +13,13 @@ init_by_lua_block {
 };
 
 $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
-$ENV{TEST_NGINX_REDIS_PORT} ||= 6379;
+$ENV{TEST_NGINX_REDIS_PORT} ||= 6380;
+$ENV{TEST_NGINX_REDIS_PORT_SL1} ||= 6381;
+$ENV{TEST_NGINX_REDIS_PORT_SL2} ||= 6382;
+$ENV{TEST_NGINX_SENTINEL_PORT1} ||= 6390;
+$ENV{TEST_NGINX_SENTINEL_PORT2} ||= 6391;
+$ENV{TEST_NGINX_SENTINEL_PORT3} ||= 6392;
+$ENV{TEST_NGINX_SENTINEL_PORT_AUTH} ||= 6393;
 
 no_long_string();
 run_tests();
@@ -23,24 +30,26 @@ __DATA__
 --- http_config eval: $::HttpConfig
 --- config
 location /t {
-	content_by_lua_block {
-		local rc = require("resty.redis.connector").new()
+    content_by_lua_block {
+        local rc = require("resty.redis.connector").new()
+        local rs = require("resty.redis.sentinel")
+
+        local sentinel, err = rc:connect{ url = "redis://127.0.0.1:$TEST_NGINX_SENTINEL_PORT1" }
+        assert(sentinel and not err, "sentinel should connect without errors but got " .. tostring(err))
 
-		local sentinel, err = rc:connect{ url = "redis://127.0.0.1:6381" }
-		assert(sentinel and not err, "sentinel should connect without errors")
+        local master, err = rs.get_master(sentinel, "mymaster")
 
-		local master, err = require("resty.redis.sentinel").get_master(
-			sentinel,
-			"mymaster"
-		)
+        assert(master and not err, "get_master should return the master")
 
-		assert(master and not err, "get_master should return the master")
+        assert(master.host == "127.0.0.1" and tonumber(master.port) == $TEST_NGINX_REDIS_PORT,
+            "host should be 127.0.0.1 and port should be $TEST_NGINX_REDIS_PORT")
 
-		assert(master.host == "127.0.0.1" and tonumber(master.port) == 6379,
-			"host should be 127.0.0.1 and port should be 6379")
+        master, err = rs.get_master(sentinel, "invalid-mymaster")
 
-		sentinel:close()
-	}
+        assert(not master and err, "invalid master name should result in error")
+
+        sentinel:close()
+    }
 }
 --- request
 GET /t
@@ -52,22 +61,22 @@ GET /t
 --- http_config eval: $::HttpConfig
 --- config
 location /t {
-	content_by_lua_block {
-		local rc = require("resty.redis.connector").new()
+    content_by_lua_block {
+        local rc = require("resty.redis.connector").new()
 
-		local master, err = rc:connect({
+        local master, err = rc:connect({
             url = "sentinel://mymaster:m/3",
             sentinels = {
-                { host = "127.0.0.1", port = 6381 }
+                { host = "127.0.0.1", port = $TEST_NGINX_SENTINEL_PORT1 }
             }
         })
 
-		assert(master and not err, "get_master should return the master")
+        assert(master and not err, "get_master should return the master")
         assert(master:set("foo", "bar"), "set should run without error")
         assert(master:get("foo") == "bar", "get(foo) should return bar")
 
-	    master:close()
-	}
+        master:close()
+    }
 }
 --- request
 GET /t
@@ -81,25 +90,27 @@ GET /t
 location /t {
     content_by_lua_block {
         local rc = require("resty.redis.connector").new()
+        local rs = require("resty.redis.sentinel")
 
-        local sentinel, err = rc:connect{ url = "redis://127.0.0.1:6381" }
+        local sentinel, err = rc:connect{ url = "redis://127.0.0.1:$TEST_NGINX_SENTINEL_PORT1" }
         assert(sentinel and not err, "sentinel should connect without error")
 
-        local slaves, err = require("resty.redis.sentinel").get_slaves(
-            sentinel,
-            "mymaster"
-        )
+        local slaves, err = rs.get_slaves(sentinel, "mymaster")
 
         assert(slaves and not err, "slaves should be returned without error")
 
-		local slaveports = { ["6378"] = false, ["6380"] = false }
+        local slaveports = { ["$TEST_NGINX_REDIS_PORT_SL1"] = false, ["$TEST_NGINX_REDIS_PORT_SL2"] = false }
+
+        for _,slave in ipairs(slaves) do
+            slaveports[tostring(slave.port)] = true
+        end
+
+        assert(slaveports["$TEST_NGINX_REDIS_PORT_SL1"] == true and slaveports["$TEST_NGINX_REDIS_PORT_SL2"] == true,
+            "slaves should both be found")
 
-		for _,slave in ipairs(slaves) do
-			slaveports[tostring(slave.port)] = true
-		end
+        slaves, err = rs.get_slaves(sentinel, "invalid-mymaster")
 
-		assert(slaveports["6378"] == true and slaveports["6380"] == true,
-			"slaves should both be found")
+        assert(not slaves and err, "invalid master name should result in error")
 
         sentinel:close()
     }
@@ -117,50 +128,50 @@ location /t {
     content_by_lua_block {
         local rc = require("resty.redis.connector").new()
 
-        local sentinel, err = rc:connect({ url = "redis://127.0.0.1:6381" })
-		assert(sentinel and not err, "sentinel should connect without error")
+        local sentinel, err = rc:connect({ url = "redis://127.0.0.1:$TEST_NGINX_SENTINEL_PORT1" })
+        assert(sentinel and not err, "sentinel should connect without error")
 
         local slaves, err = require("resty.redis.sentinel").get_slaves(
-			sentinel,
-			"mymaster"
-		)
+            sentinel,
+            "mymaster"
+        )
 
-		assert(slaves and not err, "slaves should be returned without error")
+        assert(slaves and not err, "slaves should be returned without error")
 
-		local slaveports = { ["6378"] = false, ["6380"] = false }
+        local slaveports = { ["$TEST_NGINX_REDIS_PORT_SL1"] = false, ["$TEST_NGINX_REDIS_PORT_SL2"] = false }
 
-		for _,slave in ipairs(slaves) do
-			slaveports[tostring(slave.port)] = true
-		end
+        for _,slave in ipairs(slaves) do
+            slaveports[tostring(slave.port)] = true
+        end
 
-		assert(slaveports["6378"] == true and slaveports["6380"] == true,
-			"slaves should both be found")
+        assert(slaveports["$TEST_NGINX_REDIS_PORT_SL1"] == true and slaveports["$TEST_NGINX_REDIS_PORT_SL2"] == true,
+            "slaves should both be found")
 
-		-- connect to one and remove it
-		local r = require("resty.redis.connector").new():connect({
-			port = 6378,
-		})
+        -- connect to one and remove it
+        local r = require("resty.redis.connector").new():connect({
+            port = $TEST_NGINX_REDIS_PORT_SL1,
+        })
         r:slaveof("127.0.0.1", 7000)
 
         ngx.sleep(9)
 
         local slaves, err = require("resty.redis.sentinel").get_slaves(
-			sentinel,
-			"mymaster"
-		)
+            sentinel,
+            "mymaster"
+        )
 
-		assert(slaves and not err, "slaves should be returned without error")
+        assert(slaves and not err, "slaves should be returned without error")
 
-		local slaveports = { ["6378"] = false, ["6380"] = false }
+        local slaveports = { ["$TEST_NGINX_REDIS_PORT_SL1"] = false, ["$TEST_NGINX_REDIS_PORT_SL2"] = false }
 
-		for _,slave in ipairs(slaves) do
-			slaveports[tostring(slave.port)] = true
-		end
+        for _,slave in ipairs(slaves) do
+            slaveports[tostring(slave.port)] = true
+        end
 
-		assert(slaveports["6378"] == false and slaveports["6380"] == true,
-			"only 6380 should be found")
+        assert(slaveports["$TEST_NGINX_REDIS_PORT_SL1"] == false and slaveports["$TEST_NGINX_REDIS_PORT_SL2"] == true,
+            "only $TEST_NGINX_REDIS_PORT_SL2 should be found")
 
-        r:slaveof("127.0.0.1", 6379)
+        r:slaveof("127.0.0.1", $TEST_NGINX_REDIS_PORT)
 
         sentinel:close()
     }
@@ -181,9 +192,9 @@ location /t {
 
         local params = {
             sentinels = {
-                { host = "127.0.0.1", port = 6381 },
-                { host = "127.0.0.1", port = 6382 },
-                { host = "127.0.0.1", port = 6383 },
+                { host = "127.0.0.1", port = $TEST_NGINX_SENTINEL_PORT1 },
+                { host = "127.0.0.1", port = $TEST_NGINX_SENTINEL_PORT2 },
+                { host = "127.0.0.1", port = $TEST_NGINX_SENTINEL_PORT3 },
             },
             master_name = "mymaster",
             role = "master",
@@ -214,9 +225,9 @@ location /t {
 
         local params = {
             sentinels = {
-                { host = "127.0.0.1", port = 6381 },
-                { host = "127.0.0.1", port = 6382 },
-                { host = "127.0.0.1", port = 6383 },
+                { host = "127.0.0.1", port = $TEST_NGINX_SENTINEL_PORT1 },
+                { host = "127.0.0.1", port = $TEST_NGINX_SENTINEL_PORT2 },
+                { host = "127.0.0.1", port = $TEST_NGINX_SENTINEL_PORT3 },
             },
             master_name = "mymaster",
             role = "slave",
@@ -226,10 +237,9 @@ location /t {
         local sentinel = require("resty.redis.sentinel")
         sentinel.get_slaves = function()
             return {
-                { host = "127.0.0.1", port = 6380 },
-                { host = "127.0.0.1", port = 6378 },
-                { host = "127.0.0.1", port = 6377 },
-                { host = "134.123.51.2", port = 6380 },
+                { host = "127.0.0.1", port = $TEST_NGINX_REDIS_PORT_SL1 },
+                { host = "127.0.0.1", port = $TEST_NGINX_REDIS_PORT_SL2 },
+                { host = "134.123.51.2", port = $TEST_NGINX_REDIS_PORT_SL1 },
             }
         end
 
@@ -241,3 +251,29 @@ location /t {
 GET /t
 --- no_error_log
 [error]
+
+=== TEST 6: connect with acl
+--- http_config eval: $::HttpConfig
+--- config
+location /t {
+    content_by_lua_block {
+        local rc = require("resty.redis.connector").new()
+        local redis, err = rc:connect({
+            username = "redisuser",
+            password = "redisuserpass",
+            sentinels = {
+                { host = "127.0.0.1", port = $TEST_NGINX_SENTINEL_PORT_AUTH }
+            },
+            master_name = "mymaster",
+            sentinel_username = "sentineluser",
+            sentinel_username = "sentineluserpass",
+        })
+        assert(redis and not err, "redis should connect without error")
+        local username = assert(redis:acl("whoami"))
+        assert(username == "redisuser", "should connect as 'redisuser' but got " .. tostring(username))
+    }
+}
+--- request
+GET /t
+--- no_error_log
+[error]
diff --git a/util/lua-releng b/util/lua-releng
index e3dea2d..3c29735 100755
--- a/util/lua-releng
+++ b/util/lua-releng
@@ -40,7 +40,7 @@ for my $file (map glob, qw{ *.lua lib/*.lua lib/*/*.lua lib/*/*/*.lua lib/*/*/*/
     close $in;
 
     #print "Checking use of Lua global variables in file $file ...\n";
-    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)\$'");
+    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)\$'");
     #file_contains($file, "attempt to write to undeclared variable");
     system("grep -H -n -E --color '.{120}' $file");
 }

Debdiff

File lists identical (after any substitutions)

No differences were encountered in the control files

More details

Full run details