├── .github └── workflows │ └── main.yml ├── .gitignore ├── COPYING ├── Dockerfile ├── LICENSE ├── Makefile.am ├── README.rst ├── autogen.sh ├── configure.ac ├── docker-compose.yml ├── docker-entrypoint.sh ├── m4 ├── ax_code_coverage.m4 └── ax_pthread.m4 └── src ├── Makefile.am ├── cluster.c ├── cluster.h ├── core.c ├── core.h ├── crc16.c ├── crc16.h ├── sentinel.c ├── sentinel.h ├── sha1.c ├── sha1.h ├── tests ├── assets │ ├── hashslot-keys.txt │ ├── tls-ca-certificate.crt │ ├── tls-ca-certificate.key │ ├── tls-certificate.crt │ └── tls-certificate.key ├── clustered.ASK-reply-messages.vtc ├── clustered.MOVED-reply-messages.vtc ├── clustered.basics-using-private-pool.vtc ├── clustered.basics-using-shared-pool.vtc ├── clustered.template.vtc ├── runner.sh ├── standalone.6000000.TLS.vtc ├── standalone.6000000.sentinels.vtc.disabled ├── standalone.VCL-temperature-and-discard.vtc ├── standalone.auto-role.vtc ├── standalone.basics-using-private-pool.vtc ├── standalone.basics-with-multiple-servers-and-private-pool.vtc ├── standalone.basics-with-multiple-servers-and-shared-pool.vtc ├── standalone.blocked-workers.vtc ├── standalone.command-execution-timeout.vtc ├── standalone.easy-command-timeout.vtc ├── standalone.password-protected-instances.vtc ├── standalone.prometheus-stats.vtc ├── standalone.proxied-methods.vtc ├── standalone.subnets-using-private-pool.vtc ├── standalone.subnets-using-shared-pool.vtc └── standalone.template.vtc ├── vmod_redis.c └── vmod_redis.vcc /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-24.04 10 | 11 | strategy: 12 | matrix: 13 | cc: 14 | - gcc 15 | - clang 16 | make_target: 17 | - check 18 | - distcheck 19 | configure_flags: 20 | - '' 21 | include: 22 | - cc: gcc 23 | make_target: lcov 24 | configure_flags: --enable-code-coverage 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Install packages 30 | run: | 31 | sudo apt update 32 | sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_SUSPEND=non-empty-value apt install -y \ 33 | automake autotools-dev lcov libedit-dev libev-dev \ 34 | libncurses-dev libpcre2-dev libssl-dev libtool python3-docutils \ 35 | python3-sphinx 36 | 37 | - name: Install Varnish Cache 38 | run: | 39 | git clone https://github.com/varnishcache/varnish-cache.git ./varnish 40 | pushd varnish 41 | ./autogen.sh 42 | CC='${{ matrix.cc }}' ./configure 43 | make -sj32 44 | sudo make PREFIX='/usr/local' install 45 | sudo ldconfig 46 | popd 47 | 48 | - name: Install hiredis 49 | run: | 50 | wget --no-check-certificate https://github.com/redis/hiredis/archive/v1.3.0.zip -O hiredis-1.3.0.zip 51 | unzip hiredis-*.zip 52 | pushd hiredis-*/ 53 | make USE_SSL=1 54 | sudo make USE_SSL=1 PREFIX='/usr/local' install 55 | sudo ldconfig 56 | popd 57 | 58 | - name: Install Redis 59 | run: | 60 | wget --no-check-certificate https://github.com/redis/redis/archive/refs/tags/8.0.0.tar.gz -O redis-8.0.0.tar.gz 61 | tar zxvf redis-*.tar.gz 62 | pushd redis-*/ 63 | make BUILD_TLS=yes 64 | sudo make BUILD_TLS=yes PREFIX='/usr/local' install 65 | sudo ldconfig 66 | popd 67 | 68 | - name: Build & test VMOD 69 | run: | 70 | ./autogen.sh 71 | CC='${{ matrix.cc }}' ./configure --prefix=/usr ${{ matrix.configure_flags }} 72 | make -j4 73 | make ${{ matrix.make_target }} -j1 74 | 75 | - name: Push code coverage 76 | if: ${{ matrix.make_target == 'lcov' }} 77 | run: | 78 | cp libvmod-redis-*-coverage.info codecov.info 79 | bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -t ${{ secrets.CODECOV_TOKEN }} 80 | 81 | - name: Show test report 82 | if: ${{ failure() }} 83 | run: | 84 | cat src/test-suite.log || exit 0 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.env 2 | 3 | Makefile 4 | Makefile.in 5 | .deps/ 6 | .libs/ 7 | *.o 8 | *.lo 9 | *.la 10 | *~ 11 | *.[1-9] 12 | *.log 13 | *.trs 14 | 15 | /aclocal.m4 16 | /autom4te.cache/ 17 | /build-aux/ 18 | /config.h 19 | /config.h.in 20 | /config.log 21 | /config.status 22 | /configure 23 | /libtool 24 | /stamp-h1 25 | /m4/libtool.m4 26 | /m4/ltoptions.m4 27 | /m4/ltsugar.m4 28 | /m4/ltversion.m4 29 | /m4/lt~obsolete.m4 30 | 31 | /src/vcc_*_if.c 32 | /src/vcc_*_if.h 33 | /src/vmod_*rst 34 | /src/vmod_vcs_version.txt 35 | 36 | /.vagrant/ 37 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2025 Carlos Abalde 2 | 3 | You're free to use and distribute this under terms in the 4 | LICENSE file. 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:noble-20250127 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | 5 | RUN groupadd -g 5000 dev \ 6 | && useradd -u 5000 -g 5000 -m -s /bin/bash dev 7 | 8 | RUN apt update \ 9 | && apt install -y \ 10 | apt-transport-https \ 11 | automake \ 12 | autotools-dev \ 13 | bindfs \ 14 | binutils \ 15 | curl \ 16 | dpkg-dev \ 17 | git \ 18 | gpg \ 19 | graphviz \ 20 | jq \ 21 | less \ 22 | libedit-dev \ 23 | libev-dev \ 24 | libjemalloc-dev \ 25 | libncurses-dev \ 26 | libpcre2-dev \ 27 | libssl-dev \ 28 | libtool \ 29 | make \ 30 | nano \ 31 | netcat-traditional \ 32 | pkg-config \ 33 | python3 \ 34 | python3-docutils \ 35 | python3-sphinx \ 36 | python3-venv \ 37 | tar \ 38 | telnet \ 39 | unzip \ 40 | wget \ 41 | && apt clean \ 42 | && rm -rf /var/lib/apt/lists/* 43 | 44 | RUN git clone https://github.com/varnishcache/varnish-cache.git /tmp/varnish \ 45 | && cd /tmp/varnish \ 46 | && ./autogen.sh \ 47 | && ./configure \ 48 | && make \ 49 | && make PREFIX='/usr/local' install \ 50 | && ldconfig 51 | 52 | RUN cd /tmp \ 53 | && wget --no-check-certificate https://github.com/redis/hiredis/archive/v1.3.0.zip -O hiredis-1.3.0.zip \ 54 | && unzip hiredis-*.zip \ 55 | && rm -f hiredis-*.zip \ 56 | && cd hiredis* \ 57 | && make USE_SSL=1 \ 58 | && make USE_SSL=1 PREFIX='/usr/local' install \ 59 | && ldconfig 60 | 61 | RUN cd /tmp \ 62 | && wget --no-check-certificate https://github.com/redis/redis/archive/refs/tags/8.0.0.tar.gz -O redis-8.0.0.tar.gz \ 63 | && tar zxvf redis-*.tar.gz \ 64 | && rm -f redis-*.tar.gz \ 65 | && cd redis-* \ 66 | && make BUILD_TLS=yes \ 67 | && make BUILD_TLS=yes PREFIX='/usr/local' install \ 68 | && ldconfig 69 | 70 | COPY ./docker-entrypoint.sh / 71 | ENTRYPOINT ["/docker-entrypoint.sh"] 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2025 Carlos Abalde 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 -I ${VARNISHAPI_DATAROOTDIR}/aclocal 2 | 3 | SUBDIRS = src 4 | 5 | dist_doc_DATA = README.rst LICENSE 6 | 7 | # lcov support 8 | 9 | CODE_COVERAGE_OUTPUT_DIRECTORY = lcov 10 | CODE_COVERAGE_IGNORE_PATTERN = "/usr/*" crc16.c sha1.c 11 | CODE_COVERAGE_LCOV_RMOPTS = --ignore-errors unused 12 | CODE_COVERAGE_GENHTML_OPTIONS = --prefix $(abs_top_srcdir) 13 | 14 | @CODE_COVERAGE_RULES@ 15 | 16 | lcov: check-code-coverage 17 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | .. image:: https://github.com/carlosabalde/libvmod-redis/actions/workflows/main.yml/badge.svg?branch=master 3 | :alt: GitHub Actions CI badge 4 | :target: https://github.com/carlosabalde/libvmod-redis/actions 5 | .. image:: https://codecov.io/gh/carlosabalde/libvmod-redis/branch/master/graph/badge.svg 6 | :alt: Codecov badge 7 | :target: https://codecov.io/gh/carlosabalde/libvmod-redis 8 | 9 | VMOD using the `synchronous hiredis library API `_ to access Redis servers from VCL. For an alternative using libvalkey + Valkey, please check the `libvmod-valkey VMOD `_ project. 10 | 11 | Highlights: 12 | 13 | * **Full support for execution of LUA scripts** (i.e. ``EVAL`` command), including optimistic automatic execution of ``EVALSHA`` commands. 14 | * **All Redis reply data types are supported**, including partial support to access to components of simple (i.e. not nested) array replies. 15 | * **Redis pipelines are not (and won't be) supported**. LUA scripting, which is fully supported by the VMOD, it's a much more flexible alternative to pipelines for atomic execution and minimizing latency. Pipelines are hard to use and error prone, specially when using the ``WATCH`` command. 16 | * **Support for classic Redis deployments** using multiple replicated Redis servers **and for clustered deployments based on Redis Cluster**. 17 | * **Support for multiple databases and multiple Redis connections**, local to each Varnish worker thread, or shared using one or more pools. 18 | * **Support for smart command execution**, selecting the destination server according with the preferred role (i.e. master or slave) and with distance and healthiness metrics collected during execution. 19 | * **Support for Redis Sentinel**, allowing automatic discovery of sick / healthy servers and changes in their roles. 20 | 21 | Please, check out `the project wiki `_ for some extra information and useful links. 22 | 23 | Looking for official support for this VMOD? Please, contact `Allenta Consulting `_, a `Varnish Software Premium partner `_. 24 | 25 | SYNOPSIS 26 | ======== 27 | 28 | import redis; 29 | 30 | :: 31 | 32 | ## 33 | ## Subnets. 34 | ## 35 | 36 | Function subnets(STRING masks="") 37 | 38 | ## 39 | ## Sentinels. 40 | ## 41 | 42 | Function sentinels( 43 | STRING locations="", 44 | INT period=60, 45 | INT connection_timeout=500, 46 | INT command_timeout=0, 47 | ENUM { RESP2, RESP3, default } protocol="default", 48 | BOOL tls=false, 49 | STRING tls_cafile="", 50 | STRING tls_capath="", 51 | STRING tls_certfile="", 52 | STRING tls_keyfile="", 53 | STRING tls_sni="", 54 | STRING password="") 55 | 56 | ## 57 | ## Proxy. 58 | ## 59 | 60 | # Instance selection. 61 | Function VOID use(STRING db) 62 | 63 | # Proxied methods. 64 | Method VOID .add_server(..., STRING db="") 65 | Function VOID command(..., STRING db="") 66 | Function VOID timeout(..., STRING db="") 67 | Function VOID retries(..., STRING db="") 68 | ... 69 | Method STRING .stats(..., STRING db="") 70 | Method INT .counter(..., STRING db="") 71 | 72 | ## 73 | ## Databases. 74 | ## 75 | 76 | # Constructor. 77 | Object db( 78 | STRING location="", 79 | ENUM { master, slave, auto, cluster } type="auto", 80 | INT connection_timeout=1000, 81 | INT connection_ttl=0, 82 | INT command_timeout=0, 83 | INT max_command_retries=0, 84 | BOOL shared_connections=true, 85 | INT max_connections=128, 86 | ENUM { RESP2, RESP3, default } protocol="default", 87 | BOOL tls=false, 88 | STRING tls_cafile="", 89 | STRING tls_capath="", 90 | STRING tls_certfile="", 91 | STRING tls_keyfile="", 92 | STRING tls_sni="", 93 | STRING user="", 94 | STRING password="", 95 | INT sickness_ttl=60, 96 | BOOL ignore_slaves=false, 97 | INT max_cluster_hops=32) 98 | Method VOID .add_server( 99 | STRING location, 100 | ENUM { master, slave, auto, cluster } type) 101 | 102 | # Command execution. 103 | Method VOID .command(STRING name) 104 | Method VOID .timeout(INT command_timeout) 105 | Method VOID .retries(INT max_command_retries) 106 | Method VOID .push(STRING arg) 107 | Method VOID .execute(BOOL master=true) 108 | Method VOID .easy_execute(STRING command, [STRING command_args...], BOOL master=true, INT command_timeout, INT max_command_retries) 109 | 110 | # Access to replies. 111 | Method BOOL .replied() 112 | 113 | Method BOOL .reply_is_error() 114 | Method BOOL .reply_is_nil() 115 | Method BOOL .reply_is_status() 116 | Method BOOL .reply_is_integer() 117 | Method BOOL .reply_is_boolean() 118 | Method BOOL .reply_is_double() 119 | Method BOOL .reply_is_string() 120 | Method BOOL .reply_is_array() 121 | 122 | Method STRING .get_reply() 123 | 124 | Method STRING .get_error_reply() 125 | Method STRING .get_status_reply() 126 | Method INT .get_integer_reply() 127 | Method BOOL .get_boolean_reply() 128 | Method REAL .get_double_reply() 129 | Method STRING .get_string_reply() 130 | 131 | Method INT .get_array_reply_length() 132 | Method BOOL .array_reply_is_error(INT index) 133 | Method BOOL .array_reply_is_nil(INT index) 134 | Method BOOL .array_reply_is_status(INT index) 135 | Method BOOL .array_reply_is_integer(INT index) 136 | Method BOOL .array_reply_is_boolean(INT index) 137 | Method BOOL .array_reply_is_double(INT index) 138 | Method BOOL .array_reply_is_string(INT index) 139 | Method BOOL .array_reply_is_array(INT index) 140 | Method STRING .get_array_reply_value(INT index) 141 | 142 | # Other. 143 | Method VOID .free() 144 | Method STRING .stats( 145 | ENUM { json, prometheus } format="json", 146 | BOOL stream=0, 147 | STRING prometheus_name_prefix="vmod_redis_", 148 | BOOL prometheus_default_labels=1, 149 | STRING prometheus_extra_labels="") 150 | Method INT .counter(STRING name) 151 | 152 | EXAMPLES 153 | ======== 154 | 155 | Single server 156 | ------------- 157 | 158 | :: 159 | 160 | sub vcl_init { 161 | # VMOD configuration: simple case, keeping up to one Redis connection 162 | # per Varnish worker thread. 163 | new db = redis.db( 164 | location="192.168.1.100:6379", 165 | type=master, 166 | connection_timeout=500, 167 | shared_connections=false, 168 | max_connections=1); 169 | } 170 | 171 | sub vcl_deliver { 172 | # Simple command execution. 173 | db.command("SET"); 174 | db.push("foo"); 175 | db.push("Hello world!"); 176 | db.execute(); 177 | 178 | # Alternatively, the same can be achieved with one single command 179 | db.easy_execute("SET", "foo", "Hello world!"); 180 | 181 | # LUA scripting. 182 | db.command("EVAL"); 183 | db.push({" 184 | redis.call('SET', KEYS[1], ARGV[1]) 185 | redis.call('SET', KEYS[2], ARGV[1]) 186 | "}); 187 | db.push("2"); 188 | db.push("foo"); 189 | db.push("bar"); 190 | db.push("Atomic hello world!"); 191 | db.execute(); 192 | 193 | # Array replies, checking & accessing to reply. 194 | db.command("MGET"); 195 | db.push("foo"); 196 | db.push("bar"); 197 | db.execute(); 198 | if ((db.reply_is_array()) && 199 | (db.get_array_reply_length() == 2)) { 200 | set resp.http.X-Foo = db.get_array_reply_value(0); 201 | set resp.http.X-Bar = db.get_array_reply_value(1); 202 | } 203 | } 204 | 205 | Multiple servers 206 | ---------------- 207 | 208 | :: 209 | 210 | sub vcl_init { 211 | # VMOD configuration: master-slave replication, keeping up to two 212 | # Redis connections per Varnish worker thread (up to one to the master 213 | # server & up to one to the closest slave server). 214 | redis.subnets( 215 | masks={" 216 | 0 192.168.1.102/32, 217 | 1 192.168.1.103/32, 218 | 2 0.0.0.0/32 219 | "}); 220 | new db = redis.db( 221 | location="192.168.1.100:6379", 222 | type=master, 223 | connection_timeout=500, 224 | shared_connections=false, 225 | max_connections=2); 226 | db.add_server("192.168.1.101:6379", slave); 227 | db.add_server("192.168.1.102:6379", slave); 228 | db.add_server("192.168.1.103:6379", slave); 229 | } 230 | 231 | sub vcl_deliver { 232 | # SET submitted to the master server. 233 | db.command("SET"); 234 | db.push("foo"); 235 | db.push("Hello world!"); 236 | db.execute(); 237 | 238 | # GET submitted to one of the slave servers. 239 | db.command("GET"); 240 | db.push("foo"); 241 | db.execute(false); 242 | set req.http.X-Foo = db.get_string_reply(); 243 | } 244 | 245 | Clustered setup 246 | --------------- 247 | 248 | :: 249 | 250 | sub vcl_init { 251 | # VMOD configuration: clustered setup, keeping up to 100 Redis 252 | # connections per server, all shared between all Varnish worker threads. 253 | # Two initial cluster servers are provided; remaining servers are 254 | # automatically discovered. 255 | new db = redis.db( 256 | location="192.168.1.100:6379", 257 | type=cluster, 258 | connection_timeout=500, 259 | shared_connections=true, 260 | max_connections=128, 261 | max_cluster_hops=16); 262 | db.add_server("192.168.1.101:6379", cluster); 263 | } 264 | 265 | sub vcl_deliver { 266 | # SET internally routed to the destination server. 267 | db.command("SET"); 268 | db.push("foo"); 269 | db.push("Hello world!"); 270 | db.execute(); 271 | 272 | # GET internally routed to the destination server. 273 | db.command("GET"); 274 | db.push("foo"); 275 | db.execute(false); 276 | set req.http.X-Foo = db.get_string_reply(); 277 | } 278 | 279 | INSTALLATION 280 | ============ 281 | 282 | The source tree is based on autotools to configure the building, and does also have the necessary bits in place to do functional unit tests using the varnishtest tool. 283 | 284 | **Beware this project contains multiples branches (master, 4.1, 4.0, etc.). Please, select the branch to be used depending on your Varnish Cache version (Varnish trunk → master, Varnish 4.1.x → 4.1, Varnish 4.0.x → 4.0, etc.).** 285 | 286 | Dependencies: 287 | 288 | * `hiredis `_ - minimalistic C Redis client library. 289 | * `libev `_ - full-featured and high-performance event loop. 290 | 291 | COPYRIGHT 292 | ========= 293 | 294 | See LICENSE for details. 295 | 296 | Public domain implementation of the SHA-1 cryptographic hash function by Steve Reid and embedded in this VMOD (required for the optimistic execution of ``EVALSHA`` commands) has been borrowed from `this project `_: 297 | 298 | * https://github.com/clibs/sha1/blob/master/sha1.c 299 | * https://github.com/clibs/sha1/blob/master/sha1.h 300 | 301 | BSD's implementation of the CRC-16 cryptographic hash function by Georges Menie & Salvatore Sanfilippo and embedded in this VMOD (required for the Redis Cluster slot calculation) has been borrowed from the `Redis project `_: 302 | 303 | * http://download.redis.io/redis-stable/src/crc16.c 304 | 305 | Copyright (c) Carlos Abalde 306 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | warn() { 4 | echo "WARNING: $@" 1>&2 5 | } 6 | 7 | case `uname -s` in 8 | Darwin) 9 | LIBTOOLIZE=glibtoolize 10 | ;; 11 | FreeBSD) 12 | LIBTOOLIZE=libtoolize 13 | ;; 14 | Linux) 15 | LIBTOOLIZE=libtoolize 16 | ;; 17 | SunOS) 18 | LIBTOOLIZE=libtoolize 19 | ;; 20 | *) 21 | warn "unrecognized platform:" `uname -s` 22 | LIBTOOLIZE=libtoolize 23 | esac 24 | 25 | automake_version=`automake --version | tr ' ' '\n' | egrep '^[0-9]\.[0-9a-z.-]+'` 26 | if [ -z "$automake_version" ] ; then 27 | warn "unable to determine automake version" 28 | else 29 | case $automake_version in 30 | 0.*|1.[0-8]|1.[0-8][.-]*) 31 | warn "automake ($automake_version) detected; 1.9 or newer recommended" 32 | ;; 33 | *) 34 | ;; 35 | esac 36 | fi 37 | 38 | # check for varnishapi.m4 in custom paths 39 | dataroot=$(pkg-config --variable=datarootdir varnishapi 2>/dev/null) 40 | if [ -z "$dataroot" ] ; then 41 | cat >&2 <<'EOF' 42 | Package varnishapi was not found in the pkg-config search path. 43 | Perhaps you should add the directory containing `varnishapi.pc' 44 | to the PKG_CONFIG_PATH environment variable 45 | EOF 46 | exit 1 47 | fi 48 | set -ex 49 | aclocal -I m4 -I ${dataroot}/aclocal 50 | $LIBTOOLIZE --copy --force 51 | autoheader 52 | automake --add-missing --copy --foreign 53 | autoconf 54 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ([2.68]) 2 | AC_INIT([libvmod-redis], [trunk], [], [vmod-redis]) 3 | AC_COPYRIGHT([Copyright (c) Carlos Abalde ]) 4 | AC_CONFIG_MACRO_DIR([m4]) 5 | AC_CONFIG_AUX_DIR([build-aux]) 6 | AC_CONFIG_SRCDIR(src/vmod_redis.vcc) 7 | AC_CONFIG_HEADER([config.h]) 8 | 9 | AC_GNU_SOURCE 10 | 11 | AM_INIT_AUTOMAKE([1.12 -Wall -Werror foreign parallel-tests]) 12 | AM_SILENT_RULES([yes]) 13 | AM_PROG_AR 14 | 15 | LT_PREREQ([2.2.6]) 16 | LT_INIT([dlopen disable-static]) 17 | 18 | ax_enable_compile_warnings=error 19 | AX_CODE_COVERAGE 20 | AX_COMPILER_FLAGS_CFLAGS 21 | AX_PTHREAD(,[AC_MSG_ERROR([Could not configure pthreads support])]) 22 | 23 | LIBS="$PTHREAD_LIBS $LIBS -lhiredis -lev" 24 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 25 | CC="$PTHREAD_CC" 26 | 27 | AC_ARG_WITH([rst2man], 28 | [ 29 | AS_HELP_STRING( 30 | [--with-rst2man=PATH], 31 | [Location of rst2man (auto)]) 32 | ], 33 | [RST2MAN="$withval"], 34 | [AC_CHECK_PROGS(RST2MAN, [rst2man rst2man.py], [])]) 35 | 36 | m4_ifndef([VARNISH_PREREQ], AC_MSG_ERROR([Need varnish.m4 -- see README.rst])) 37 | 38 | #VARNISH_PREREQ([5.0], [5.1]) 39 | VARNISH_VMODS([redis]) 40 | 41 | VMOD_TESTS="$(cd $srcdir/src && echo tests/*.vtc)" 42 | AC_SUBST(VMOD_TESTS) 43 | 44 | PKG_CHECK_VAR([LIBVARNISHAPI_LIBDIR], [varnishapi], [libdir]) 45 | AC_SUBST([VARNISH_LIBRARY_PATH], 46 | [$LIBVARNISHAPI_LIBDIR:$LIBVARNISHAPI_LIBDIR/varnish]) 47 | 48 | # Check for libhiredis. 49 | AC_CHECK_LIB( 50 | hiredis, redisCommand, [], 51 | [AC_MSG_ERROR([libvmod-redis requires libhiredis.])]) 52 | 53 | # Check for libev3 (ev_loop) / libev4 (ev_run). 54 | # -DEV_COMPAT3=1 used during build. 55 | AC_CHECK_LIB( 56 | ev, ev_loop, [], 57 | [AC_CHECK_LIB( 58 | ev, ev_run, [], 59 | [AC_MSG_ERROR([libvmod-redis requires libev.])])]) 60 | 61 | # Check for TLS support: --enable-tls / --disable-tls 62 | AC_ARG_ENABLE( 63 | tls, 64 | [ 65 | AS_HELP_STRING( 66 | [--enable-tls], 67 | [enable TLS support (default is YES)]) 68 | ], 69 | [], 70 | [enable_tls=yes]) 71 | AC_MSG_CHECKING([for TLS support]) 72 | if test "x$enable_tls" = xyes; then 73 | AC_MSG_RESULT([enabled]) 74 | AC_DEFINE( 75 | [TLS_ENABLED], 76 | [1], 77 | [TLS enabled]) 78 | 79 | AC_CHECK_LIB( 80 | crypto, CRYPTO_new_ex_data, [], 81 | [AC_MSG_ERROR([libvmod-redis requires libcrypto. Try --disable-tls.])]) 82 | 83 | AC_CHECK_LIB( 84 | ssl, SSL_CTX_new, [], 85 | [AC_MSG_ERROR([libvmod-redis requires libssl. Try --disable-tls.])]) 86 | 87 | AC_CHECK_LIB( 88 | hiredis_ssl, redisInitiateSSL, [], 89 | [AC_MSG_ERROR([libvmod-redis requires libhiredis_ssl. Try --disable-tls.])]) 90 | 91 | AC_CHECK_LIB( 92 | varnish-sslhelper, VSSLH_status, [], []) 93 | else 94 | AC_MSG_RESULT([disabled]) 95 | fi 96 | 97 | AC_CONFIG_FILES([ 98 | Makefile 99 | src/Makefile 100 | ]) 101 | AC_OUTPUT 102 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # 4 | # Cheat sheet: 5 | # $ echo -e "UID=$(id -u)\nGID=$(id -g)" > .env 6 | # $ docker compose up --build --detach 7 | # $ docker compose exec --user dev --workdir /mnt/host dev bash 8 | # $ ./autogen.sh 9 | # $ ./configure 10 | # $ make 11 | # $ make check 12 | # $ docker compose down --volumes --remove-orphans 13 | # 14 | 15 | version: '3.7' 16 | 17 | name: libvmod-redis-master 18 | 19 | services: 20 | dev: 21 | hostname: dev 22 | build: 23 | context: . 24 | no_cache: true 25 | privileged: true 26 | environment: 27 | HOST_UID: ${UID:?} 28 | HOST_GID: ${GID:?} 29 | volumes: 30 | - .:/mnt/host.raw 31 | tmpfs: 32 | - /run 33 | - /run/lock 34 | - /var/cache 35 | - /tmp:exec 36 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p /mnt/host 4 | 5 | # Beware 'privileged: true' is required for this. 6 | bindfs \ 7 | --force-user=$(id -u dev) \ 8 | --force-group=$(id -g dev) \ 9 | --create-for-user=$HOST_UID \ 10 | --create-for-group=$HOST_GID \ 11 | --chown-ignore \ 12 | --chgrp-ignore \ 13 | /mnt/host.raw \ 14 | /mnt/host 15 | 16 | tail -f /dev/null 17 | -------------------------------------------------------------------------------- /m4/ax_code_coverage.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_code_coverage.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CODE_COVERAGE() 8 | # 9 | # DESCRIPTION 10 | # 11 | # Defines CODE_COVERAGE_CPPFLAGS, CODE_COVERAGE_CFLAGS, 12 | # CODE_COVERAGE_CXXFLAGS and CODE_COVERAGE_LIBS which should be included 13 | # in the CPPFLAGS, CFLAGS CXXFLAGS and LIBS/LIBADD variables of every 14 | # build target (program or library) which should be built with code 15 | # coverage support. Also defines CODE_COVERAGE_RULES which should be 16 | # substituted in your Makefile; and $enable_code_coverage which can be 17 | # used in subsequent configure output. CODE_COVERAGE_ENABLED is defined 18 | # and substituted, and corresponds to the value of the 19 | # --enable-code-coverage option, which defaults to being disabled. 20 | # 21 | # Test also for gcov program and create GCOV variable that could be 22 | # substituted. 23 | # 24 | # Note that all optimisation flags in CFLAGS must be disabled when code 25 | # coverage is enabled. 26 | # 27 | # Usage example: 28 | # 29 | # configure.ac: 30 | # 31 | # AX_CODE_COVERAGE 32 | # 33 | # Makefile.am: 34 | # 35 | # @CODE_COVERAGE_RULES@ 36 | # my_program_LIBS = ... $(CODE_COVERAGE_LIBS) ... 37 | # my_program_CPPFLAGS = ... $(CODE_COVERAGE_CPPFLAGS) ... 38 | # my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ... 39 | # my_program_CXXFLAGS = ... $(CODE_COVERAGE_CXXFLAGS) ... 40 | # 41 | # This results in a "check-code-coverage" rule being added to any 42 | # Makefile.am which includes "@CODE_COVERAGE_RULES@" (assuming the module 43 | # has been configured with --enable-code-coverage). Running `make 44 | # check-code-coverage` in that directory will run the module's test suite 45 | # (`make check`) and build a code coverage report detailing the code which 46 | # was touched, then print the URI for the report. 47 | # 48 | # In earlier versions of this macro, CODE_COVERAGE_LDFLAGS was defined 49 | # instead of CODE_COVERAGE_LIBS. They are both still defined, but use of 50 | # CODE_COVERAGE_LIBS is preferred for clarity; CODE_COVERAGE_LDFLAGS is 51 | # deprecated. They have the same value. 52 | # 53 | # This code was derived from Makefile.decl in GLib, originally licenced 54 | # under LGPLv2.1+. 55 | # 56 | # LICENSE 57 | # 58 | # Copyright (c) 2012, 2016 Philip Withnall 59 | # Copyright (c) 2012 Xan Lopez 60 | # Copyright (c) 2012 Christian Persch 61 | # Copyright (c) 2012 Paolo Borelli 62 | # Copyright (c) 2012 Dan Winship 63 | # Copyright (c) 2015 Bastien ROUCARIES 64 | # 65 | # This library is free software; you can redistribute it and/or modify it 66 | # under the terms of the GNU Lesser General Public License as published by 67 | # the Free Software Foundation; either version 2.1 of the License, or (at 68 | # your option) any later version. 69 | # 70 | # This library is distributed in the hope that it will be useful, but 71 | # WITHOUT ANY WARRANTY; without even the implied warranty of 72 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 73 | # General Public License for more details. 74 | # 75 | # You should have received a copy of the GNU Lesser General Public License 76 | # along with this program. If not, see . 77 | 78 | #serial 20 79 | 80 | AC_DEFUN([AX_CODE_COVERAGE],[ 81 | dnl Check for --enable-code-coverage 82 | AC_REQUIRE([AC_PROG_SED]) 83 | 84 | # allow to override gcov location 85 | AC_ARG_WITH([gcov], 86 | [AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])], 87 | [_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov], 88 | [_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov]) 89 | 90 | AC_MSG_CHECKING([whether to build with code coverage support]) 91 | AC_ARG_ENABLE([code-coverage], 92 | AS_HELP_STRING([--enable-code-coverage], 93 | [Whether to enable code coverage support]),, 94 | enable_code_coverage=no) 95 | 96 | AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test x$enable_code_coverage = xyes]) 97 | AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage]) 98 | AC_MSG_RESULT($enable_code_coverage) 99 | 100 | AS_IF([ test "$enable_code_coverage" = "yes" ], [ 101 | # check for gcov 102 | AC_CHECK_TOOL([GCOV], 103 | [$_AX_CODE_COVERAGE_GCOV_PROG_WITH], 104 | [:]) 105 | AS_IF([test "X$GCOV" = "X:"], 106 | [AC_MSG_ERROR([gcov is needed to do coverage])]) 107 | AC_SUBST([GCOV]) 108 | 109 | dnl Check if gcc is being used 110 | AS_IF([ test "$GCC" = "no" ], [ 111 | AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage]) 112 | ]) 113 | 114 | AC_CHECK_PROG([LCOV], [lcov], [lcov]) 115 | AC_CHECK_PROG([GENHTML], [genhtml], [genhtml]) 116 | 117 | AS_IF([ test -z "$LCOV" ], [ 118 | AC_MSG_ERROR([To enable code coverage reporting you must have lcov installed]) 119 | ]) 120 | 121 | AS_IF([ test -z "$GENHTML" ], [ 122 | AC_MSG_ERROR([Could not find genhtml from the lcov package]) 123 | ]) 124 | 125 | dnl Build the code coverage flags 126 | dnl Define CODE_COVERAGE_LDFLAGS for backwards compatibility 127 | CODE_COVERAGE_CPPFLAGS="-DNDEBUG" 128 | CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" 129 | CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" 130 | CODE_COVERAGE_LIBS="-lgcov" 131 | CODE_COVERAGE_LDFLAGS="$CODE_COVERAGE_LIBS" 132 | 133 | AC_SUBST([CODE_COVERAGE_CPPFLAGS]) 134 | AC_SUBST([CODE_COVERAGE_CFLAGS]) 135 | AC_SUBST([CODE_COVERAGE_CXXFLAGS]) 136 | AC_SUBST([CODE_COVERAGE_LIBS]) 137 | AC_SUBST([CODE_COVERAGE_LDFLAGS]) 138 | 139 | [CODE_COVERAGE_RULES_CHECK=' 140 | -$(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) -k check 141 | $(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) code-coverage-capture 142 | '] 143 | [CODE_COVERAGE_RULES_CAPTURE=' 144 | $(code_coverage_v_lcov_cap)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --capture --output-file "$(CODE_COVERAGE_OUTPUT_FILE).tmp" --test-name "$(call code_coverage_sanitize,$(PACKAGE_NAME)-$(PACKAGE_VERSION))" --no-checksum --compat-libtool $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_OPTIONS) 145 | $(code_coverage_v_lcov_ign)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --remove "$(CODE_COVERAGE_OUTPUT_FILE).tmp" "/tmp/*" $(CODE_COVERAGE_IGNORE_PATTERN) --output-file "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_RMOPTS) 146 | -@rm -f $(CODE_COVERAGE_OUTPUT_FILE).tmp 147 | $(code_coverage_v_genhtml)LANG=C $(GENHTML) $(code_coverage_quiet) $(addprefix --prefix ,$(CODE_COVERAGE_DIRECTORY)) --output-directory "$(CODE_COVERAGE_OUTPUT_DIRECTORY)" --title "$(PACKAGE_NAME)-$(PACKAGE_VERSION) Code Coverage" --legend --show-details "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_GENHTML_OPTIONS) 148 | @echo "file://$(abs_builddir)/$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html" 149 | '] 150 | [CODE_COVERAGE_RULES_CLEAN=' 151 | clean: code-coverage-clean 152 | distclean: code-coverage-clean 153 | code-coverage-clean: 154 | -$(LCOV) --directory $(top_builddir) -z 155 | -rm -rf $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_FILE).tmp $(CODE_COVERAGE_OUTPUT_DIRECTORY) 156 | -find . \( -name "*.gcda" -o -name "*.gcno" -o -name "*.gcov" \) -delete 157 | '] 158 | ], [ 159 | [CODE_COVERAGE_RULES_CHECK=' 160 | @echo "Need to reconfigure with --enable-code-coverage" 161 | '] 162 | CODE_COVERAGE_RULES_CAPTURE="$CODE_COVERAGE_RULES_CHECK" 163 | CODE_COVERAGE_RULES_CLEAN='' 164 | ]) 165 | 166 | [CODE_COVERAGE_RULES=' 167 | # Code coverage 168 | # 169 | # Optional: 170 | # - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting. 171 | # Multiple directories may be specified, separated by whitespace. 172 | # (Default: $(top_builddir)) 173 | # - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated 174 | # by lcov for code coverage. (Default: 175 | # $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info) 176 | # - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage 177 | # reports to be created. (Default: 178 | # $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage) 179 | # - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage, 180 | # set to 0 to disable it and leave empty to stay with the default. 181 | # (Default: empty) 182 | # - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov 183 | # instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) 184 | # - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov 185 | # instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) 186 | # - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov 187 | # - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the 188 | # collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) 189 | # - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov 190 | # instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) 191 | # - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering 192 | # lcov instance. (Default: empty) 193 | # - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov 194 | # instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) 195 | # - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the 196 | # genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) 197 | # - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml 198 | # instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) 199 | # - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore 200 | # 201 | # The generated report will be titled using the $(PACKAGE_NAME) and 202 | # $(PACKAGE_VERSION). In order to add the current git hash to the title, 203 | # use the git-version-gen script, available online. 204 | 205 | # Optional variables 206 | CODE_COVERAGE_DIRECTORY ?= $(top_builddir) 207 | CODE_COVERAGE_OUTPUT_FILE ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info 208 | CODE_COVERAGE_OUTPUT_DIRECTORY ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage 209 | CODE_COVERAGE_BRANCH_COVERAGE ?= 210 | CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ 211 | --rc lcov_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) 212 | CODE_COVERAGE_LCOV_SHOPTS ?= $(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) 213 | CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool "$(GCOV)" 214 | CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= $(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) 215 | CODE_COVERAGE_LCOV_OPTIONS ?= $(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) 216 | CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?= 217 | CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) 218 | CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\ 219 | $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ 220 | --rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) 221 | CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULTS) 222 | CODE_COVERAGE_IGNORE_PATTERN ?= 223 | 224 | code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V)) 225 | code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY)) 226 | code_coverage_v_lcov_cap_0 = @echo " LCOV --capture"\ 227 | $(CODE_COVERAGE_OUTPUT_FILE); 228 | code_coverage_v_lcov_ign = $(code_coverage_v_lcov_ign_$(V)) 229 | code_coverage_v_lcov_ign_ = $(code_coverage_v_lcov_ign_$(AM_DEFAULT_VERBOSITY)) 230 | code_coverage_v_lcov_ign_0 = @echo " LCOV --remove /tmp/*"\ 231 | $(CODE_COVERAGE_IGNORE_PATTERN); 232 | code_coverage_v_genhtml = $(code_coverage_v_genhtml_$(V)) 233 | code_coverage_v_genhtml_ = $(code_coverage_v_genhtml_$(AM_DEFAULT_VERBOSITY)) 234 | code_coverage_v_genhtml_0 = @echo " GEN " $(CODE_COVERAGE_OUTPUT_DIRECTORY); 235 | code_coverage_quiet = $(code_coverage_quiet_$(V)) 236 | code_coverage_quiet_ = $(code_coverage_quiet_$(AM_DEFAULT_VERBOSITY)) 237 | code_coverage_quiet_0 = --quiet 238 | 239 | # sanitizes the test-name: replaces with underscores: dashes and dots 240 | code_coverage_sanitize = $(subst -,_,$(subst .,_,$(1))) 241 | 242 | # Use recursive makes in order to ignore errors during check 243 | check-code-coverage:'"$CODE_COVERAGE_RULES_CHECK"' 244 | 245 | # Capture code coverage data 246 | code-coverage-capture: code-coverage-capture-hook'"$CODE_COVERAGE_RULES_CAPTURE"' 247 | 248 | # Hook rule executed before code-coverage-capture, overridable by the user 249 | code-coverage-capture-hook: 250 | 251 | '"$CODE_COVERAGE_RULES_CLEAN"' 252 | 253 | GITIGNOREFILES ?= 254 | GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY) 255 | 256 | A''M_DISTCHECK_CONFIGURE_FLAGS ?= 257 | A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage 258 | 259 | .PHONY: check-code-coverage code-coverage-capture code-coverage-capture-hook code-coverage-clean 260 | '] 261 | 262 | AC_SUBST([CODE_COVERAGE_RULES]) 263 | m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([CODE_COVERAGE_RULES])]) 264 | ]) 265 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CFLAGS = $(VARNISHAPI_CFLAGS) $(CODE_COVERAGE_CFLAGS) -Wall -DEV_COMPAT3=1 2 | AM_LDFLAGS = $(VARNISHAPI_LIBS) $(VMOD_LDFLAGS) $(CODE_COVERAGE_LDFLAGS) 3 | 4 | vmod_LTLIBRARIES = libvmod_redis.la 5 | 6 | libvmod_redis_la_SOURCES = \ 7 | crc16.c crc16.h \ 8 | sha1.c sha1.h \ 9 | cluster.c cluster.h \ 10 | core.c core.h \ 11 | sentinel.c sentinel.h \ 12 | vmod_redis.c 13 | 14 | nodist_libvmod_redis_la_SOURCES = \ 15 | vcc_redis_if.c \ 16 | vcc_redis_if.h 17 | 18 | dist_man_MANS = vmod_redis.3 19 | 20 | @BUILD_VMOD_REDIS@ 21 | 22 | AM_TESTS_ENVIRONMENT = \ 23 | PATH="$(VMOD_TEST_PATH)" \ 24 | LD_LIBRARY_PATH="$(VARNISH_LIBRARY_PATH)" 25 | TEST_EXTENSIONS = .vtc 26 | VTC_LOG_COMPILER = $(abs_srcdir)/tests/runner.sh varnishtest 27 | AM_VTC_LOG_FLAGS = -v -Dvmod_redis="$(VMOD_REDIS)" 28 | 29 | TESTS = @VMOD_TESTS@ 30 | 31 | EXTRA_DIST = \ 32 | tests/assets/hashslot-keys.txt \ 33 | tests/assets/tls-ca-certificate.crt \ 34 | tests/assets/tls-ca-certificate.key \ 35 | tests/assets/tls-certificate.crt \ 36 | tests/assets/tls-certificate.key \ 37 | tests/runner.sh \ 38 | vmod_redis.vcc \ 39 | $(VMOD_TESTS) 40 | 41 | DISTCLEANFILES = vmod_vcs_version.txt 42 | -------------------------------------------------------------------------------- /src/cluster.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cache/cache.h" 12 | 13 | #include "crc16.h" 14 | #include "core.h" 15 | #include "cluster.h" 16 | 17 | #define BANNED_COMMANDS "|INFO|MULTI|EXEC|SLAVEOF|REPLICAOF|CONFIG|SHUTDOWN|SCRIPT|" 18 | #define KEY_INDEX3_COMMANDS "|EVAL|EVALSHA|" 19 | 20 | #define CLUSTER_DISCOVERY_COMMAND "CLUSTER SLOTS" 21 | 22 | static void unsafe_discover_slots( 23 | VRT_CTX, struct vmod_redis_db *db, vcl_state_t *config, redis_server_t *server); 24 | 25 | static int get_key_index(const char *command); 26 | static unsigned get_cluster_slot(const char *key); 27 | 28 | void 29 | discover_cluster_slots( 30 | VRT_CTX, struct vmod_redis_db *db, vcl_state_t *config, redis_server_t *server) 31 | { 32 | Lck_Lock(&config->mutex); 33 | Lck_Lock(&db->mutex); 34 | unsafe_discover_slots(ctx, db, config, server); 35 | Lck_Unlock(&db->mutex); 36 | Lck_Unlock(&config->mutex); 37 | } 38 | 39 | redisReply * 40 | cluster_execute( 41 | VRT_CTX, struct vmod_redis_db *db, vcl_state_t *config, task_state_t *state, 42 | struct timeval timeout, unsigned max_retries, unsigned argc, const char *argv[], 43 | unsigned *retries, unsigned master) 44 | { 45 | // Initializations. 46 | redisReply *result = NULL; 47 | 48 | // Can the command be executed in a clustered setup? 49 | int index = get_key_index(argv[0]); 50 | if ((index > 0) && (index < argc)) { 51 | // Initializations. 52 | unsigned slot = get_cluster_slot(argv[index]); 53 | unsigned hops = db->cluster.max_hops > 0 ? db->cluster.max_hops : UINT_MAX; 54 | unsigned asking = 0; 55 | unsigned hop = 0; 56 | redis_server_t *server = NULL; 57 | 58 | // Execute command, retrying and following redirections up to 59 | // some limit. 60 | while (result == NULL) { 61 | // Execute command: 62 | // - server != NULL ==> only include 'server' in the execution plan. 63 | // - !master ==> use READONLY + READWRITE when dealing with slaves. 64 | // - unknown slot ==> random server selection. 65 | result = redis_execute( 66 | ctx, db, state, timeout, max_retries, argc, argv, 67 | retries, server, asking, master, slot); 68 | 69 | // Reset flags. 70 | asking = 0; 71 | hop = 0; 72 | server = NULL; 73 | 74 | // Check reply. 75 | if (result != NULL) { 76 | // Is this a MOVED or ASK error reply? 77 | if ((result->type == REDIS_REPLY_ERROR) && 78 | ((strncmp(result->str, "MOVED", 5) == 0) || 79 | (strncmp(result->str, "ASK", 3) == 0))) { 80 | // Extract location (e.g. ASK 3999 127.0.0.1:6381). 81 | char *ptr = strchr(result->str, ' '); 82 | AN(ptr); 83 | char *location = strchr(ptr + 1, ' '); 84 | AN(location); 85 | location++; 86 | 87 | // Set hop flag. 88 | hop = 1; 89 | 90 | // Get config & database locks. 91 | Lck_Lock(&config->mutex); 92 | Lck_Lock(&db->mutex); 93 | 94 | // Add / fetch server. 95 | server = unsafe_add_redis_server( 96 | ctx, db, config, location, REDIS_SERVER_TBD_ROLE); 97 | AN(server); 98 | 99 | // ASK vs. MOVED. 100 | if (strncmp(result->str, "MOVED", 3) == 0) { 101 | // Update stats. 102 | db->stats.cluster.replies.moved++; 103 | 104 | // Rediscover the cluster topology asking to the server 105 | // in the MOVED reply (or to any other server if that 106 | // one fails). Giving priority to the server in the 107 | // MOVED reply ensures that the right topology will be 108 | // discovered even when it has not yet been propagated 109 | // to the whole cluster. 110 | // 111 | // Even though using 'server' in the next execution plan 112 | // is not strictly required because the cluster topology 113 | // has just been rediscovered, this allows handling in a 114 | // nice way rw commands sent to ro slaves. 115 | // 116 | // XXX: at the moment this implementation may result in 117 | // multiple threads executing multiple -serialized- 118 | // cluster discoveries. 119 | unsafe_discover_slots(ctx, db, config, server); 120 | } else { 121 | // Update stats. 122 | db->stats.cluster.replies.ask++; 123 | 124 | // Next attempt should send a ASKING command to the 125 | // server in the ASK reply. 126 | asking = 1; 127 | } 128 | 129 | // Release config & database locks. 130 | Lck_Unlock(&db->mutex); 131 | Lck_Unlock(&config->mutex); 132 | 133 | // Release reply object. 134 | freeReplyObject(result); 135 | result = NULL; 136 | 137 | // Execution completed: some reply, excluding cluster 138 | // redirections. 139 | } else { 140 | break; 141 | } 142 | } 143 | 144 | // Try again? 145 | if (result == NULL) { 146 | if (hop && (hops > 0) && (*retries <= max_retries)) { 147 | hops--; 148 | } else { 149 | break; 150 | } 151 | } 152 | } 153 | 154 | // Too many redirections? 155 | if (hops == 0) { 156 | REDIS_LOG_ERROR(ctx, 157 | "Too many redirections while executing cluster command (command=%s, db=%s)", 158 | argv[0], db->name); 159 | } 160 | 161 | // Invalid Redis Cluster command. 162 | } else { 163 | REDIS_LOG_ERROR(ctx, 164 | "Invalid cluster command (command=%s, db=%s)", 165 | argv[0], db->name); 166 | } 167 | 168 | // Done! 169 | return result; 170 | } 171 | 172 | /****************************************************************************** 173 | * UTILITIES. 174 | *****************************************************************************/ 175 | 176 | static void 177 | unsafe_add_slot( 178 | VRT_CTX, struct vmod_redis_db *db, vcl_state_t *config, unsigned start, 179 | unsigned stop, char *host, int port, enum REDIS_SERVER_ROLE role) 180 | { 181 | // Assertions. 182 | Lck_AssertHeld(&config->mutex); 183 | Lck_AssertHeld(&db->mutex); 184 | 185 | // Add / update server. 186 | char location[256]; 187 | snprintf(location, sizeof(location), "%s:%d", host, port); 188 | redis_server_t *server = unsafe_add_redis_server(ctx, db, config, location, role); 189 | AN(server); 190 | 191 | // Register slots. 192 | for (int i = start; i <= stop; i++) { 193 | server->cluster.slots[i] = 1; 194 | } 195 | } 196 | 197 | static unsigned 198 | unsafe_discover_slots_aux( 199 | VRT_CTX, struct vmod_redis_db *db, vcl_state_t *config, redis_server_t *server) 200 | { 201 | // Assertions. 202 | Lck_AssertHeld(&config->mutex); 203 | Lck_AssertHeld(&db->mutex); 204 | assert(server->location.type == REDIS_SERVER_LOCATION_HOST_TYPE); 205 | 206 | // Log event. 207 | REDIS_LOG_INFO(ctx, 208 | "Discovery of cluster topology started (db=%s, server=%s)", 209 | db->name, server->location.raw); 210 | 211 | // Initializations. 212 | unsigned done = 0; 213 | 214 | // Create context. 215 | redisContext *rcontext; 216 | if ((db->connection_timeout.tv_sec > 0) || 217 | (db->connection_timeout.tv_usec > 0)) { 218 | rcontext = redisConnectWithTimeout( 219 | server->location.parsed.address.host, 220 | server->location.parsed.address.port, 221 | db->connection_timeout); 222 | } else { 223 | rcontext = redisConnect( 224 | server->location.parsed.address.host, 225 | server->location.parsed.address.port); 226 | } 227 | 228 | // Check context. 229 | if ((rcontext != NULL) && (!rcontext->err)) { 230 | // Optionally setup TLS & submit AUTH / HELLO command. 231 | REDIS_BLESS_CONTEXT( 232 | ctx, rcontext, server->db, 233 | "Failed to initialize cluster discovery connection", 234 | "db=%s, server=%s", 235 | server->db->name, server->location.raw); 236 | 237 | // Do not continue if failed to initialize the connection. 238 | if (rcontext != NULL) { 239 | // Set command execution timeout. 240 | int tr = redisSetTimeout(rcontext, db->command_timeout); 241 | if (tr != REDIS_OK) { 242 | REDIS_LOG_ERROR(ctx, 243 | "Failed to set cluster discovery command execution timeout (error=%d, db=%s, server=%s)", 244 | tr, server->db->name, server->location.raw); 245 | } 246 | 247 | // Send command. 248 | redisReply *reply = redisCommand(rcontext, CLUSTER_DISCOVERY_COMMAND); 249 | 250 | // Check reply. 251 | if ((!rcontext->err) && 252 | (reply != NULL) && 253 | (reply->type == REDIS_REPLY_ARRAY)) { 254 | // Reset previous slots. 255 | redis_server_t *iserver; 256 | for (unsigned iweight = 0; iweight < NREDIS_SERVER_WEIGHTS; iweight++) { 257 | for (enum REDIS_SERVER_ROLE irole = 0; irole < NREDIS_SERVER_ROLES; irole++) { 258 | VTAILQ_FOREACH(iserver, &db->servers[iweight][irole], list) { 259 | for (int i = 0; i < NREDIS_CLUSTER_SLOTS; i++) { 260 | iserver->cluster.slots[i] = 0; 261 | } 262 | } 263 | } 264 | } 265 | 266 | // Extract slots. 267 | for (int i = 0; i < reply->elements; i++) { 268 | if ((reply->element[i]->type == REDIS_REPLY_ARRAY) && 269 | (reply->element[i]->elements >= 3) && 270 | (reply->element[i]->element[0]->type == REDIS_REPLY_INTEGER) && 271 | (reply->element[i]->element[1]->type == REDIS_REPLY_INTEGER) && 272 | (reply->element[i]->element[2]->type == REDIS_REPLY_ARRAY) && 273 | (reply->element[i]->element[2]->elements >= 2) && 274 | (reply->element[i]->element[2]->element[0]->type == REDIS_REPLY_STRING) && 275 | (reply->element[i]->element[2]->element[1]->type == REDIS_REPLY_INTEGER)) { 276 | // Extract slot data. 277 | int start = reply->element[i]->element[0]->integer; 278 | int end = reply->element[i]->element[1]->integer; 279 | 280 | // Check slot data. 281 | if ((start >= 0) && (start < NREDIS_CLUSTER_SLOTS) && 282 | (end >= 0) && (end < NREDIS_CLUSTER_SLOTS)) { 283 | unsafe_add_slot( 284 | ctx, db, config, start, end, 285 | reply->element[i]->element[2]->element[0]->str, 286 | reply->element[i]->element[2]->element[1]->integer, 287 | REDIS_SERVER_MASTER_ROLE); 288 | 289 | // Extract slave servers data. 290 | for (int j = 3; j < reply->element[i]->elements; j++) { 291 | if ((reply->element[i]->element[j]->type == REDIS_REPLY_ARRAY) && 292 | (reply->element[i]->element[j]->elements >= 2) && 293 | (reply->element[i]->element[j]->element[0]->type == REDIS_REPLY_STRING) && 294 | (reply->element[i]->element[j]->element[1]->type == REDIS_REPLY_INTEGER)) { 295 | unsafe_add_slot( 296 | ctx, db, config, start, end, 297 | reply->element[i]->element[j]->element[0]->str, 298 | reply->element[i]->element[j]->element[1]->integer, 299 | REDIS_SERVER_SLAVE_ROLE); 300 | } 301 | } 302 | } 303 | } 304 | } 305 | 306 | // Stop execution. 307 | done = 1; 308 | db->stats.cluster.discoveries.total++; 309 | } else { 310 | REDIS_LOG_ERROR(ctx, 311 | "Failed to execute cluster discovery command (error=%d, db=%s, server=%s): %s", 312 | rcontext->err, db->name, server->location.raw, 313 | HIREDIS_ERRSTR(rcontext, reply)); 314 | db->stats.cluster.discoveries.failed++; 315 | } 316 | 317 | // Release reply. 318 | if (reply != NULL) { 319 | freeReplyObject(reply); 320 | } 321 | } else { 322 | db->stats.cluster.discoveries.failed++; 323 | } 324 | } else { 325 | if (rcontext != NULL) { 326 | REDIS_LOG_ERROR(ctx, 327 | "Failed to establish cluster discovery connection (error=%d, db=%s, server=%s): %s", 328 | rcontext->err, db->name, server->location.raw, HIREDIS_ERRSTR(rcontext)); 329 | } else { 330 | REDIS_LOG_ERROR(ctx, 331 | "Failed to establish cluster discovery connection (db=%s, server=%s)", 332 | db->name, server->location.raw); 333 | } 334 | db->stats.cluster.discoveries.failed++; 335 | } 336 | 337 | // Release context. 338 | if (rcontext != NULL) { 339 | redisFree(rcontext); 340 | } 341 | 342 | // Done. 343 | return done; 344 | } 345 | 346 | static void 347 | unsafe_discover_slots( 348 | VRT_CTX, struct vmod_redis_db *db, vcl_state_t *config, redis_server_t *server) 349 | { 350 | // Assertions. 351 | Lck_AssertHeld(&config->mutex); 352 | Lck_AssertHeld(&db->mutex); 353 | 354 | // Contact already known servers and try to fetch the slots-servers mapping. 355 | // Always use the provided server instance in the first place. 356 | if (!unsafe_discover_slots_aux(ctx, db, config, server)) { 357 | for (unsigned iweight = 0; iweight < NREDIS_SERVER_WEIGHTS; iweight++) { 358 | for (enum REDIS_SERVER_ROLE irole = 0; irole < NREDIS_SERVER_ROLES; irole++) { 359 | redis_server_t *iserver; 360 | VTAILQ_FOREACH(iserver, &db->servers[iweight][irole], list) { 361 | CHECK_OBJ_NOTNULL(iserver, REDIS_SERVER_MAGIC); 362 | if ((iserver != server) && 363 | (unsafe_discover_slots_aux(ctx, db, config, iserver))) { 364 | // Lists of servers are only modified on a successful 365 | // discovery ==> it's safe to iterate on these data 366 | // structures because once they are modified the 367 | // iteration will finish. 368 | return; 369 | } 370 | } 371 | } 372 | } 373 | } 374 | } 375 | 376 | static int 377 | get_key_index(const char *command) 378 | { 379 | // Initializations. 380 | char buffer[64]; 381 | snprintf(buffer, sizeof(buffer), "|%s|", command); 382 | 383 | // Some commands (e.g. INFO) are explicitly banned returning -1. Some other 384 | // commands (e.g. EVAL) are explicitly handled to return the correct 385 | // location of the key value. Finally, all other commands are assumed to 386 | // contain the key as the first argument after the command name. This is 387 | // indeed the case for most commands, and when it is not true the cluster 388 | // redirection will point to the right node anyway. 389 | // 390 | // XXX: beware that cluster redirections trigger expensive cluster 391 | // rediscoveries ==> they must be avoided at all costs. 392 | if (strcasestr(BANNED_COMMANDS, buffer) != NULL) { 393 | return -1; 394 | } else if (strcasestr(KEY_INDEX3_COMMANDS, buffer) != NULL) { 395 | return 3; 396 | } 397 | return 1; 398 | } 399 | 400 | static unsigned 401 | get_cluster_slot(const char *key) 402 | { 403 | // Start-end indexes of '{'' and '}'. 404 | int s, e; 405 | 406 | // Search the first occurrence of '{'. 407 | int keylen = strlen(key); 408 | for (s = 0; s < keylen; s++) { 409 | if (key[s] == '{') { 410 | break; 411 | } 412 | } 413 | 414 | // No '{'? Hash the whole key. This is the base case. 415 | if (s == keylen) { 416 | return crc16(key, keylen) & (NREDIS_CLUSTER_SLOTS - 1); 417 | } 418 | 419 | // '{' found? Check if we have the corresponding '}'. 420 | for (e = s+1; e < keylen; e++){ 421 | if (key[e] == '}') { 422 | break; 423 | } 424 | } 425 | 426 | // No '}' or nothing between {}? Hash the whole key. 427 | if ((e == keylen) || (e == s + 1)) { 428 | return crc16(key, keylen) & (NREDIS_CLUSTER_SLOTS - 1); 429 | } 430 | 431 | // If we are here there is both a '{' and a '}' on its right. Hash 432 | // what is in the middle between '{' and '}'. 433 | return crc16(key + s + 1, e - s - 1) & (NREDIS_CLUSTER_SLOTS - 1); 434 | } 435 | -------------------------------------------------------------------------------- /src/cluster.h: -------------------------------------------------------------------------------- 1 | #ifndef CLUSTER_H_INCLUDED 2 | #define CLUSTER_H_INCLUDED 3 | 4 | #include 5 | 6 | #include "core.h" 7 | 8 | void discover_cluster_slots( 9 | VRT_CTX, struct vmod_redis_db *db, vcl_state_t *config, redis_server_t *server); 10 | 11 | redisReply *cluster_execute( 12 | VRT_CTX, struct vmod_redis_db *db, vcl_state_t *config, task_state_t *state, 13 | struct timeval timeout, unsigned max_retries, unsigned argc, const char *argv[], 14 | unsigned *retries, unsigned master); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/core.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_H_INCLUDED 2 | #define CORE_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #ifdef TLS_ENABLED 8 | #include 9 | #endif 10 | #include 11 | #include 12 | 13 | #include "vqueue.h" 14 | 15 | #define NREDIS_SERVER_ROLES 3 16 | #define NREDIS_SERVER_WEIGHTS 4 17 | #define NREDIS_CLUSTER_SLOTS 16384 18 | 19 | enum REDIS_PROTOCOL { 20 | REDIS_PROTOCOL_DEFAULT = 0, 21 | REDIS_PROTOCOL_RESP2 = 2, 22 | REDIS_PROTOCOL_RESP3 = 3 23 | }; 24 | 25 | // Required lock ordering to avoid deadlocks: 26 | // 1. vcl_state->mutex. 27 | // 2. vmod_redis_db->mutex. 28 | 29 | // WARNING: ordering of roles in this enumeration is relevant when populating 30 | // an execution plan. 31 | enum REDIS_SERVER_ROLE { 32 | REDIS_SERVER_SLAVE_ROLE = 0, 33 | REDIS_SERVER_MASTER_ROLE = 1, 34 | REDIS_SERVER_TBD_ROLE = 2 35 | }; 36 | 37 | enum REDIS_SERVER_LOCATION_TYPE { 38 | REDIS_SERVER_LOCATION_HOST_TYPE, 39 | REDIS_SERVER_LOCATION_SOCKET_TYPE 40 | }; 41 | 42 | typedef struct redis_server { 43 | // Object marker. 44 | #define REDIS_SERVER_MAGIC 0xac587b11 45 | unsigned magic; 46 | 47 | // Database. 48 | struct vmod_redis_db *db; 49 | 50 | // Location (allocated in the heap). 51 | struct { 52 | const char *raw; 53 | enum REDIS_SERVER_LOCATION_TYPE type; 54 | union { 55 | struct { 56 | const char *host; 57 | unsigned port; 58 | } address; 59 | const char *path; 60 | } parsed; 61 | } location; 62 | 63 | // Role (rw field to be protected by db->mutex). 64 | enum REDIS_SERVER_ROLE role; 65 | 66 | // Weight. 67 | unsigned weight; 68 | 69 | // Shared pool. 70 | struct { 71 | // Condition variable. 72 | pthread_cond_t cond; 73 | 74 | // Contexts (rw fields -allocated in the heap- to be protected by 75 | // db->mutex and the associated condition variable). 76 | unsigned ncontexts; 77 | VTAILQ_HEAD(,redis_context) free_contexts; 78 | VTAILQ_HEAD(,redis_context) busy_contexts; 79 | } pool; 80 | 81 | // Redis Cluster state (rw fields to be protected by db->mutex). 82 | struct { 83 | unsigned slots[NREDIS_CLUSTER_SLOTS]; 84 | } cluster; 85 | 86 | // Sickness timestamps (rw fields to be protected by db->mutex): last time 87 | // the server was flagged as sick, and expiration of the last sickness 88 | // condition. 89 | struct { 90 | time_t tst; 91 | time_t exp; 92 | } sickness; 93 | 94 | // Tail queue. 95 | VTAILQ_ENTRY(redis_server) list; 96 | } redis_server_t; 97 | 98 | typedef struct redis_context { 99 | // Object marker. 100 | #define REDIS_CONTEXT_MAGIC 0xe11eaa70 101 | unsigned magic; 102 | 103 | // Server. 104 | redis_server_t *server; 105 | 106 | // Data (allocated in the heap). 107 | redisContext *rcontext; 108 | unsigned version; 109 | time_t tst; 110 | 111 | // Tail queue. 112 | VTAILQ_ENTRY(redis_context) list; 113 | } redis_context_t; 114 | 115 | struct vcl_state; 116 | typedef struct vcl_state vcl_state_t; 117 | 118 | struct vmod_redis_db { 119 | // Object marker. 120 | unsigned magic; 121 | #define VMOD_REDIS_DATABASE_MAGIC 0xef35182b 122 | 123 | // Mutex. 124 | struct lock mutex; 125 | 126 | // Configuration. 127 | // XXX: required because PRIV_VCL pointers are not available when the 128 | // VMOD releases database instances. This should be fixed in future 129 | // Varnish releases. 130 | vcl_state_t *config; 131 | 132 | // General options (allocated in the heap). 133 | const char *name; 134 | struct timeval connection_timeout; 135 | unsigned connection_ttl; 136 | struct timeval command_timeout; 137 | unsigned max_command_retries; 138 | unsigned shared_connections; 139 | unsigned max_connections; 140 | enum REDIS_PROTOCOL protocol; 141 | #ifdef TLS_ENABLED 142 | redisSSLContext *tls_ssl_ctx; 143 | #endif 144 | const char *user; 145 | const char *password; 146 | time_t sickness_ttl; 147 | unsigned ignore_slaves; 148 | 149 | // Redis servers (rw field -allocated in the heap- to be protected by the 150 | // associated mutex), clustered by weight & role. 151 | VTAILQ_HEAD(,redis_server) servers[NREDIS_SERVER_WEIGHTS][NREDIS_SERVER_ROLES]; 152 | 153 | // Redis Cluster options. 154 | struct { 155 | unsigned enabled; 156 | unsigned max_hops; 157 | } cluster; 158 | 159 | // Stats (rw fields to be protected by the associated mutex). 160 | struct stats { 161 | struct { 162 | // Number of successfully created servers. 163 | uint64_t total; 164 | // Number of failures while trying to create new servers. 165 | uint64_t failed; 166 | } servers; 167 | 168 | struct { 169 | // Number of successfully created connections. 170 | uint64_t total; 171 | // Number of failures while trying to create new connections. 172 | uint64_t failed; 173 | // Number of (established and probably healthy) connections dropped. 174 | struct { 175 | uint64_t error; 176 | uint64_t hung_up; 177 | uint64_t overflow; 178 | uint64_t ttl; 179 | uint64_t version; 180 | uint64_t sick; 181 | } dropped; 182 | } connections; 183 | 184 | struct { 185 | // Number of times some worker thread have been blocked waiting for 186 | // a free connection. 187 | uint64_t blocked; 188 | } workers; 189 | 190 | struct { 191 | // Number of successfully executed commands (this includes Redis 192 | // error replies). 193 | uint64_t total; 194 | // Number of failed command executions (this does not include Redis 195 | // error replies). If retries have been requested, each failed try 196 | // is considered as a separate command. 197 | uint64_t failed; 198 | // Number of retried command executions (this includes both 199 | // successful and failed executions). 200 | uint64_t retried; 201 | // Number of successfully executed commands returning a Redis error 202 | // reply. 203 | uint64_t error; 204 | // Number of NOSCRIPT error replies while executing EVALSHA 205 | // commands. 206 | uint64_t noscript; 207 | } commands; 208 | 209 | struct { 210 | struct { 211 | // Number of successfully executed discoveries. 212 | uint64_t total; 213 | // Number of failed discoveries (this includes connection 214 | // failures, unexpected responses, etc.). 215 | uint64_t failed; 216 | } discoveries; 217 | struct { 218 | // Number of MOVED replies. 219 | uint64_t moved; 220 | // Number of ASK replies. 221 | uint64_t ask; 222 | } replies; 223 | } cluster; 224 | } stats; 225 | }; 226 | 227 | typedef struct task_state { 228 | // Object marker. 229 | #define TASK_STATE_MAGIC 0xa6bc103e 230 | unsigned magic; 231 | 232 | // Private contexts (allocated in the heap). 233 | unsigned ncontexts; 234 | VTAILQ_HEAD(,redis_context) contexts; 235 | 236 | // Current database. 237 | struct vmod_redis_db *db; 238 | 239 | // Redis command: 240 | // - Database. 241 | // - Arguments (allocated in the session workspace). 242 | // - Reply (allocated in the heap). 243 | #define MAX_REDIS_COMMAND_ARGS 128 244 | struct { 245 | struct vmod_redis_db *db; 246 | struct timeval timeout; 247 | unsigned max_retries; 248 | unsigned argc; 249 | const char *argv[MAX_REDIS_COMMAND_ARGS]; 250 | redisReply *reply; 251 | } command; 252 | } task_state_t; 253 | 254 | typedef struct subnet { 255 | // Object marker. 256 | #define SUBNET_MAGIC 0x27facd57 257 | unsigned magic; 258 | 259 | // Weight. 260 | unsigned weight; 261 | 262 | // Address and mask stored in unsigned 32 bit variables (in_addr.s_addr) 263 | // using host byte oder. 264 | // XXX: only IPv4 subnets supported. 265 | struct in_addr address; 266 | struct in_addr mask; 267 | 268 | // Tail queue. 269 | VTAILQ_ENTRY(subnet) list; 270 | } subnet_t; 271 | 272 | typedef struct database { 273 | // Object marker. 274 | #define DATABASE_MAGIC 0x9200fda1 275 | unsigned magic; 276 | 277 | // Database. 278 | struct vmod_redis_db *db; 279 | 280 | // Tail queue. 281 | VTAILQ_ENTRY(database) list; 282 | } database_t; 283 | 284 | struct vcl_state { 285 | // Object marker. 286 | #define VCL_STATE_MAGIC 0x77feec11 287 | unsigned magic; 288 | 289 | // Mutex. 290 | struct lock mutex; 291 | 292 | // Subnets (rw field to be protected by the associated mutex). 293 | VTAILQ_HEAD(,subnet) subnets; 294 | 295 | // Databases (rw field to be protected by the associated mutex). 296 | VTAILQ_HEAD(,database) dbs; 297 | 298 | // Sentinel (rw fields to be protected by the associated mutex). 299 | struct { 300 | // Raw configuration. 301 | const char *locations; 302 | unsigned period; 303 | struct timeval connection_timeout; 304 | struct timeval command_timeout; 305 | enum REDIS_PROTOCOL protocol; 306 | #ifdef TLS_ENABLED 307 | unsigned tls; 308 | const char *tls_cafile; 309 | const char *tls_capath; 310 | const char *tls_certfile; 311 | const char *tls_keyfile; 312 | const char *tls_sni; 313 | #endif 314 | const char *password; 315 | 316 | // Thread reference + shared state. 317 | pthread_t thread; 318 | unsigned active; 319 | unsigned discovery; 320 | } sentinels; 321 | }; 322 | 323 | typedef struct vmod_state { 324 | // Mutex. 325 | pthread_mutex_t mutex; 326 | 327 | // Version increased on every VCL warm event (rw field protected by the 328 | // associated mutex on writes; it's ok to ignore the lock during reads). 329 | // This will be used to (1) reestablish connections binded to worker 330 | // threads; and (2) regenerate pooled connections shared between threads. 331 | unsigned version; 332 | 333 | // Varnish locks. 334 | struct { 335 | unsigned refs; 336 | struct vsc_seg *vsc_seg; 337 | struct VSC_lck *config; 338 | struct VSC_lck *db; 339 | } locks; 340 | } vmod_state_t; 341 | 342 | extern vmod_state_t vmod_state; 343 | 344 | // See: https://stackoverflow.com/a/8814003/1806102. 345 | #define HIREDIS_ERRSTR_1(rcontext) \ 346 | (rcontext->err ? rcontext->errstr : "-") 347 | #define HIREDIS_ERRSTR_2(rcontext, reply) \ 348 | (rcontext->err ? \ 349 | rcontext->errstr : \ 350 | ((reply != NULL && \ 351 | (reply->type == REDIS_REPLY_ERROR || \ 352 | reply->type == REDIS_REPLY_STATUS || \ 353 | reply->type == REDIS_REPLY_STRING)) ? reply->str : "-")) 354 | #define HIREDIS_ERRSTR_X(x, rcontext, reply, FUNC, ...) FUNC 355 | #define HIREDIS_ERRSTR(...) \ 356 | HIREDIS_ERRSTR_X(,##__VA_ARGS__, \ 357 | HIREDIS_ERRSTR_2(__VA_ARGS__), \ 358 | HIREDIS_ERRSTR_1(__VA_ARGS__)) 359 | 360 | #if HIREDIS_MAJOR >= 1 && HIREDIS_MINOR >= 0 361 | #define RESP3_ENABLED 1 362 | #define RESP3_SWITCH(a, b) a 363 | #else 364 | #define RESP3_SWITCH(a, b) b 365 | #endif 366 | 367 | #define REDIS_LOG(ctx, priority, fmt, ...) \ 368 | do { \ 369 | const struct vrt_ctx *_ctx = ctx; \ 370 | \ 371 | char *_buffer; \ 372 | if (priority <= LOG_ERR) { \ 373 | assert(asprintf( \ 374 | &_buffer, \ 375 | "[REDIS][%s:%d] %s", __func__, __LINE__, fmt) > 0); \ 376 | } else { \ 377 | assert(asprintf( \ 378 | &_buffer, \ 379 | "[REDIS] %s", fmt) > 0); \ 380 | } \ 381 | \ 382 | syslog(priority, _buffer, ##__VA_ARGS__); \ 383 | \ 384 | unsigned _tag; \ 385 | if (priority <= LOG_ERR) { \ 386 | _tag = SLT_VCL_Error; \ 387 | } else { \ 388 | _tag = SLT_VCL_Log; \ 389 | } \ 390 | if ((_ctx != NULL) && (_ctx->vsl != NULL)) { \ 391 | VSLb(_ctx->vsl, _tag, _buffer, ##__VA_ARGS__); \ 392 | } else { \ 393 | VSL(_tag, NO_VXID, _buffer, ##__VA_ARGS__); \ 394 | } \ 395 | \ 396 | free(_buffer); \ 397 | } while (0) 398 | 399 | #define REDIS_LOG_ERROR(ctx, fmt, ...) \ 400 | REDIS_LOG(ctx, LOG_ERR, fmt, ##__VA_ARGS__) 401 | #define REDIS_LOG_WARNING(ctx, fmt, ...) \ 402 | REDIS_LOG(ctx, LOG_WARNING, fmt, ##__VA_ARGS__) 403 | #define REDIS_LOG_INFO(ctx, fmt, ...) \ 404 | REDIS_LOG(ctx, LOG_INFO, fmt, ##__VA_ARGS__) 405 | 406 | #define REDIS_FAIL(ctx, result, fmt, ...) \ 407 | do { \ 408 | syslog(LOG_ALERT, "[REDIS][%s:%d] " fmt, __func__, __LINE__, ##__VA_ARGS__); \ 409 | VRT_fail(ctx, "[REDIS][%s:%d] " fmt, __func__, __LINE__, ##__VA_ARGS__); \ 410 | return result; \ 411 | } while (0) 412 | 413 | #define REDIS_FAIL_WS(ctx, result) \ 414 | REDIS_FAIL(ctx, result, "Workspace overflow") 415 | 416 | #define REDIS_FAIL_INSTANCE(ctx, result) \ 417 | REDIS_FAIL(ctx, result, "Failed to create instance") 418 | 419 | #ifdef TLS_ENABLED 420 | #define REDIS_TLS(ctx, rcontext, db, message1, message2, ...) \ 421 | do { \ 422 | if (db->tls_ssl_ctx != NULL && \ 423 | redisInitiateSSLWithContext(rcontext, db->tls_ssl_ctx) != REDIS_OK) { \ 424 | REDIS_LOG_ERROR(ctx, \ 425 | message1 " (error=%d, " message2 "): %s", \ 426 | rcontext->err, ##__VA_ARGS__, HIREDIS_ERRSTR(rcontext)); \ 427 | redisFree(rcontext); \ 428 | rcontext = NULL; \ 429 | } \ 430 | } while (0) 431 | #else 432 | #define REDIS_TLS(ctx, rcontext, db, message1, message2, ...) 433 | #endif 434 | 435 | #define REDIS_BLESS_CONTEXT(ctx, rcontext, db, message1, message2, ...) \ 436 | do { \ 437 | REDIS_TLS(ctx, rcontext, db, message1, message2, ##__VA_ARGS__); \ 438 | \ 439 | if (rcontext != NULL) { \ 440 | redisReply *reply = NULL; \ 441 | \ 442 | if (db->protocol == REDIS_PROTOCOL_DEFAULT) { \ 443 | if (db->password != NULL) { \ 444 | if (db->user != NULL) { \ 445 | reply = redisCommand(rcontext, "AUTH %s %s", db->user, db->password); \ 446 | } else { \ 447 | reply = redisCommand(rcontext, "AUTH %s", db->password); \ 448 | } \ 449 | \ 450 | if ((rcontext->err) || \ 451 | (reply == NULL) || \ 452 | (reply->type != REDIS_REPLY_STATUS) || \ 453 | (strcmp(reply->str, "OK") != 0)) { \ 454 | REDIS_LOG_ERROR(ctx, \ 455 | message1 " (error=%d, " message2 "): %s", \ 456 | rcontext->err, ##__VA_ARGS__, HIREDIS_ERRSTR(rcontext, reply)); \ 457 | redisFree(rcontext); \ 458 | rcontext = NULL; \ 459 | } \ 460 | } \ 461 | } else { \ 462 | if (db->password != NULL) { \ 463 | reply = redisCommand(rcontext, "HELLO %d AUTH %s %s", \ 464 | db->protocol, (db->user != NULL) ? db->user : "default", db->password); \ 465 | } else { \ 466 | reply = redisCommand(rcontext, "HELLO %d", db->protocol); \ 467 | } \ 468 | \ 469 | if ((rcontext->err) || \ 470 | (reply == NULL) || \ 471 | (reply->type != REDIS_REPLY_ARRAY && \ 472 | RESP3_SWITCH(reply->type != REDIS_REPLY_MAP, 1)) \ 473 | ) { \ 474 | REDIS_LOG_ERROR(ctx, \ 475 | message1 " (error=%d, " message2 "): %s", \ 476 | rcontext->err, ##__VA_ARGS__, HIREDIS_ERRSTR(rcontext, reply)); \ 477 | redisFree(rcontext); \ 478 | rcontext = NULL; \ 479 | } \ 480 | } \ 481 | \ 482 | if (reply != NULL) { \ 483 | freeReplyObject(reply); \ 484 | } \ 485 | } \ 486 | } while (0) 487 | 488 | redis_server_t *new_redis_server( 489 | struct vmod_redis_db *db, const char *location, enum REDIS_SERVER_ROLE role); 490 | void free_redis_server(redis_server_t *server); 491 | 492 | redis_context_t *new_redis_context( 493 | redis_server_t *server, redisContext *rcontext, time_t tst); 494 | void free_redis_context(redis_context_t *context); 495 | 496 | struct vmod_redis_db *new_vmod_redis_db( 497 | vcl_state_t *config, const char *name, struct timeval connection_timeout, 498 | unsigned connection_ttl, struct timeval command_timeout, unsigned max_command_retries, 499 | unsigned shared_connections, unsigned max_connections, enum REDIS_PROTOCOL protocol, 500 | #ifdef TLS_ENABLED 501 | redisSSLContext *tls_ssl_ctx, 502 | #endif 503 | const char *user, const char *password, unsigned sickness_ttl, 504 | unsigned ignore_slaves, unsigned clustered, unsigned max_cluster_hops); 505 | void free_vmod_redis_db(struct vmod_redis_db *db); 506 | 507 | task_state_t *new_task_state(); 508 | void free_task_state(task_state_t *state); 509 | 510 | vcl_state_t *new_vcl_state(); 511 | void free_vcl_state(vcl_state_t *priv); 512 | 513 | subnet_t *new_subnet(unsigned weight, struct in_addr ia4, unsigned bits); 514 | void free_subnet(subnet_t *subnet); 515 | 516 | database_t *new_database(struct vmod_redis_db *db); 517 | void free_database(database_t *db); 518 | 519 | redisReply *redis_execute( 520 | VRT_CTX, struct vmod_redis_db *db, task_state_t *state, struct timeval timeout, 521 | unsigned max_retries, unsigned argc, const char *argv[], unsigned *retries, 522 | redis_server_t *server, unsigned asking, unsigned master, unsigned slot); 523 | 524 | redis_server_t * unsafe_add_redis_server( 525 | VRT_CTX, struct vmod_redis_db *db, vcl_state_t *config, 526 | const char *location, enum REDIS_SERVER_ROLE role); 527 | 528 | #endif 529 | -------------------------------------------------------------------------------- /src/crc16.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | /* 4 | * Copyright 2001-2010 Georges Menie (www.menie.org) 5 | * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of the University of California, Berkeley nor the 17 | * names of its contributors may be used to endorse or promote products 18 | * derived from this software without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 24 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | /* CRC16 implementation according to CCITT standards. 33 | * 34 | * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the 35 | * following parameters: 36 | * 37 | * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" 38 | * Width : 16 bit 39 | * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) 40 | * Initialization : 0000 41 | * Reflect Input byte : False 42 | * Reflect Output CRC : False 43 | * Xor constant to output CRC : 0000 44 | * Output for "123456789" : 31C3 45 | */ 46 | 47 | #include "crc16.h" 48 | 49 | static const uint16_t crc16tab[256]= { 50 | 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, 51 | 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, 52 | 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, 53 | 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, 54 | 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, 55 | 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, 56 | 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, 57 | 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, 58 | 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, 59 | 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, 60 | 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, 61 | 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, 62 | 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, 63 | 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, 64 | 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, 65 | 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, 66 | 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, 67 | 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, 68 | 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, 69 | 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, 70 | 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, 71 | 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, 72 | 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, 73 | 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, 74 | 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, 75 | 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, 76 | 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, 77 | 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, 78 | 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, 79 | 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, 80 | 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, 81 | 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 82 | }; 83 | 84 | uint16_t crc16(const char *buf, int len) { 85 | int counter; 86 | uint16_t crc = 0; 87 | for (counter = 0; counter < len; counter++) 88 | crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; 89 | return crc; 90 | } 91 | -------------------------------------------------------------------------------- /src/crc16.h: -------------------------------------------------------------------------------- 1 | #ifndef CRC16_H_INCLUDED 2 | #define CRC16_H_INCLUDED 3 | 4 | #include 5 | 6 | uint16_t crc16(const char *buf, int len); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /src/sentinel.h: -------------------------------------------------------------------------------- 1 | #ifndef SENTINEL_H_INCLUDED 2 | #define SENTINEL_H_INCLUDED 3 | 4 | #include "core.h" 5 | 6 | void unsafe_sentinel_start(vcl_state_t *config); 7 | 8 | void unsafe_sentinel_discovery(vcl_state_t *config); 9 | 10 | void unsafe_sentinel_stop(vcl_state_t *config); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/sha1.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | /* 4 | SHA-1 in C 5 | By Steve Reid 6 | 100% Public Domain 7 | 8 | Test Vectors (from FIPS PUB 180-1) 9 | "abc" 10 | A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D 11 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 12 | 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 13 | A million repetitions of "a" 14 | 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F 15 | */ 16 | 17 | /* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ 18 | /* #define SHA1HANDSOFF * Copies data before messing with it. */ 19 | 20 | #define SHA1HANDSOFF 21 | 22 | #include 23 | #include 24 | 25 | /* for uint32_t */ 26 | #include 27 | 28 | #include "sha1.h" 29 | 30 | 31 | #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) 32 | 33 | /* blk0() and blk() perform the initial expand. */ 34 | /* I got the idea of expanding during the round function from SSLeay */ 35 | #if BYTE_ORDER == LITTLE_ENDIAN 36 | #define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ 37 | |(rol(block->l[i],8)&0x00FF00FF)) 38 | #elif BYTE_ORDER == BIG_ENDIAN 39 | #define blk0(i) block->l[i] 40 | #else 41 | #error "Endianness not defined!" 42 | #endif 43 | #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ 44 | ^block->l[(i+2)&15]^block->l[i&15],1)) 45 | 46 | /* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ 47 | #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); 48 | #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); 49 | #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); 50 | #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); 51 | #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); 52 | 53 | 54 | /* Hash a single 512-bit block. This is the core of the algorithm. */ 55 | 56 | void SHA1Transform( 57 | uint32_t state[5], 58 | const unsigned char buffer[64] 59 | ) 60 | { 61 | uint32_t a, b, c, d, e; 62 | 63 | typedef union 64 | { 65 | unsigned char c[64]; 66 | uint32_t l[16]; 67 | } CHAR64LONG16; 68 | 69 | #ifdef SHA1HANDSOFF 70 | CHAR64LONG16 block[1]; /* use array to appear as a pointer */ 71 | 72 | memcpy(block, buffer, 64); 73 | #else 74 | /* The following had better never be used because it causes the 75 | * pointer-to-const buffer to be cast into a pointer to non-const. 76 | * And the result is written through. I threw a "const" in, hoping 77 | * this will cause a diagnostic. 78 | */ 79 | CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer; 80 | #endif 81 | /* Copy context->state[] to working vars */ 82 | a = state[0]; 83 | b = state[1]; 84 | c = state[2]; 85 | d = state[3]; 86 | e = state[4]; 87 | /* 4 rounds of 20 operations each. Loop unrolled. */ 88 | R0(a, b, c, d, e, 0); 89 | R0(e, a, b, c, d, 1); 90 | R0(d, e, a, b, c, 2); 91 | R0(c, d, e, a, b, 3); 92 | R0(b, c, d, e, a, 4); 93 | R0(a, b, c, d, e, 5); 94 | R0(e, a, b, c, d, 6); 95 | R0(d, e, a, b, c, 7); 96 | R0(c, d, e, a, b, 8); 97 | R0(b, c, d, e, a, 9); 98 | R0(a, b, c, d, e, 10); 99 | R0(e, a, b, c, d, 11); 100 | R0(d, e, a, b, c, 12); 101 | R0(c, d, e, a, b, 13); 102 | R0(b, c, d, e, a, 14); 103 | R0(a, b, c, d, e, 15); 104 | R1(e, a, b, c, d, 16); 105 | R1(d, e, a, b, c, 17); 106 | R1(c, d, e, a, b, 18); 107 | R1(b, c, d, e, a, 19); 108 | R2(a, b, c, d, e, 20); 109 | R2(e, a, b, c, d, 21); 110 | R2(d, e, a, b, c, 22); 111 | R2(c, d, e, a, b, 23); 112 | R2(b, c, d, e, a, 24); 113 | R2(a, b, c, d, e, 25); 114 | R2(e, a, b, c, d, 26); 115 | R2(d, e, a, b, c, 27); 116 | R2(c, d, e, a, b, 28); 117 | R2(b, c, d, e, a, 29); 118 | R2(a, b, c, d, e, 30); 119 | R2(e, a, b, c, d, 31); 120 | R2(d, e, a, b, c, 32); 121 | R2(c, d, e, a, b, 33); 122 | R2(b, c, d, e, a, 34); 123 | R2(a, b, c, d, e, 35); 124 | R2(e, a, b, c, d, 36); 125 | R2(d, e, a, b, c, 37); 126 | R2(c, d, e, a, b, 38); 127 | R2(b, c, d, e, a, 39); 128 | R3(a, b, c, d, e, 40); 129 | R3(e, a, b, c, d, 41); 130 | R3(d, e, a, b, c, 42); 131 | R3(c, d, e, a, b, 43); 132 | R3(b, c, d, e, a, 44); 133 | R3(a, b, c, d, e, 45); 134 | R3(e, a, b, c, d, 46); 135 | R3(d, e, a, b, c, 47); 136 | R3(c, d, e, a, b, 48); 137 | R3(b, c, d, e, a, 49); 138 | R3(a, b, c, d, e, 50); 139 | R3(e, a, b, c, d, 51); 140 | R3(d, e, a, b, c, 52); 141 | R3(c, d, e, a, b, 53); 142 | R3(b, c, d, e, a, 54); 143 | R3(a, b, c, d, e, 55); 144 | R3(e, a, b, c, d, 56); 145 | R3(d, e, a, b, c, 57); 146 | R3(c, d, e, a, b, 58); 147 | R3(b, c, d, e, a, 59); 148 | R4(a, b, c, d, e, 60); 149 | R4(e, a, b, c, d, 61); 150 | R4(d, e, a, b, c, 62); 151 | R4(c, d, e, a, b, 63); 152 | R4(b, c, d, e, a, 64); 153 | R4(a, b, c, d, e, 65); 154 | R4(e, a, b, c, d, 66); 155 | R4(d, e, a, b, c, 67); 156 | R4(c, d, e, a, b, 68); 157 | R4(b, c, d, e, a, 69); 158 | R4(a, b, c, d, e, 70); 159 | R4(e, a, b, c, d, 71); 160 | R4(d, e, a, b, c, 72); 161 | R4(c, d, e, a, b, 73); 162 | R4(b, c, d, e, a, 74); 163 | R4(a, b, c, d, e, 75); 164 | R4(e, a, b, c, d, 76); 165 | R4(d, e, a, b, c, 77); 166 | R4(c, d, e, a, b, 78); 167 | R4(b, c, d, e, a, 79); 168 | /* Add the working vars back into context.state[] */ 169 | state[0] += a; 170 | state[1] += b; 171 | state[2] += c; 172 | state[3] += d; 173 | state[4] += e; 174 | /* Wipe variables */ 175 | a = b = c = d = e = 0; 176 | #ifdef SHA1HANDSOFF 177 | memset(block, '\0', sizeof(block)); 178 | #endif 179 | } 180 | 181 | 182 | /* SHA1Init - Initialize new context */ 183 | 184 | void SHA1Init( 185 | SHA1_CTX * context 186 | ) 187 | { 188 | /* SHA1 initialization constants */ 189 | context->state[0] = 0x67452301; 190 | context->state[1] = 0xEFCDAB89; 191 | context->state[2] = 0x98BADCFE; 192 | context->state[3] = 0x10325476; 193 | context->state[4] = 0xC3D2E1F0; 194 | context->count[0] = context->count[1] = 0; 195 | } 196 | 197 | 198 | /* Run your data through this. */ 199 | 200 | void SHA1Update( 201 | SHA1_CTX * context, 202 | const unsigned char *data, 203 | uint32_t len 204 | ) 205 | { 206 | uint32_t i; 207 | 208 | uint32_t j; 209 | 210 | j = context->count[0]; 211 | if ((context->count[0] += len << 3) < j) 212 | context->count[1]++; 213 | context->count[1] += (len >> 29); 214 | j = (j >> 3) & 63; 215 | if ((j + len) > 63) 216 | { 217 | memcpy(&context->buffer[j], data, (i = 64 - j)); 218 | SHA1Transform(context->state, context->buffer); 219 | for (; i + 63 < len; i += 64) 220 | { 221 | SHA1Transform(context->state, &data[i]); 222 | } 223 | j = 0; 224 | } 225 | else 226 | i = 0; 227 | memcpy(&context->buffer[j], &data[i], len - i); 228 | } 229 | 230 | 231 | /* Add padding and return the message digest. */ 232 | 233 | void SHA1Final( 234 | unsigned char digest[20], 235 | SHA1_CTX * context 236 | ) 237 | { 238 | unsigned i; 239 | 240 | unsigned char finalcount[8]; 241 | 242 | unsigned char c; 243 | 244 | #if 0 /* untested "improvement" by DHR */ 245 | /* Convert context->count to a sequence of bytes 246 | * in finalcount. Second element first, but 247 | * big-endian order within element. 248 | * But we do it all backwards. 249 | */ 250 | unsigned char *fcp = &finalcount[8]; 251 | 252 | for (i = 0; i < 2; i++) 253 | { 254 | uint32_t t = context->count[i]; 255 | 256 | int j; 257 | 258 | for (j = 0; j < 4; t >>= 8, j++) 259 | *--fcp = (unsigned char) t} 260 | #else 261 | for (i = 0; i < 8; i++) 262 | { 263 | finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ 264 | } 265 | #endif 266 | c = 0200; 267 | SHA1Update(context, &c, 1); 268 | while ((context->count[0] & 504) != 448) 269 | { 270 | c = 0000; 271 | SHA1Update(context, &c, 1); 272 | } 273 | SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ 274 | for (i = 0; i < 20; i++) 275 | { 276 | digest[i] = (unsigned char) 277 | ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); 278 | } 279 | /* Wipe variables */ 280 | memset(context, '\0', sizeof(*context)); 281 | memset(&finalcount, '\0', sizeof(finalcount)); 282 | } 283 | 284 | void SHA1( 285 | char *hash_out, 286 | const char *str, 287 | int len) 288 | { 289 | SHA1_CTX ctx; 290 | unsigned int ii; 291 | 292 | SHA1Init(&ctx); 293 | for (ii=0; ii 7 | 100% Public Domain 8 | */ 9 | 10 | #include "stdint.h" 11 | 12 | typedef struct 13 | { 14 | uint32_t state[5]; 15 | uint32_t count[2]; 16 | unsigned char buffer[64]; 17 | } SHA1_CTX; 18 | 19 | void SHA1Transform( 20 | uint32_t state[5], 21 | const unsigned char buffer[64] 22 | ); 23 | 24 | void SHA1Init( 25 | SHA1_CTX * context 26 | ); 27 | 28 | void SHA1Update( 29 | SHA1_CTX * context, 30 | const unsigned char *data, 31 | uint32_t len 32 | ); 33 | 34 | void SHA1Final( 35 | unsigned char digest[20], 36 | SHA1_CTX * context 37 | ); 38 | 39 | void SHA1( 40 | char *hash_out, 41 | const char *str, 42 | int len); 43 | 44 | #endif /* SHA1_H_INCLUDED */ 45 | -------------------------------------------------------------------------------- /src/tests/assets/tls-ca-certificate.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFQzCCAyugAwIBAgIJAKETN42WmkeXMA0GCSqGSIb3DQEBCwUAMDgxFjAUBgNV 3 | BAoMDWxpYnZtb2QtcmVkaXMxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0 4 | eTAeFw0yMDA1MTQwOTA5NThaFw0zMDA1MTIwOTA5NThaMDgxFjAUBgNVBAoMDWxp 5 | YnZtb2QtcmVkaXMxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTCCAiIw 6 | DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMkronFeqBVGzOTG1aT7qodwXGFq 7 | AlhwHiP+UGMQsIqSfi234eHE9mgYnJI4PRsktdE0hp7VL8d9nDJh/YLpWm7XdJ2d 8 | J5gmDrkKVSGVAb0HY3jAKIn8tR7h6xkB+9ymiYRwUrrf1Rqb/uaiUP9yy+DVy3IY 9 | Mvsun9XEKdqgSlt46AGEN1GQvR7JksUkfl1UgCO5XYBRlxtukL9X6XQQUQrcdcVN 10 | AABKYUqQRftPCAeuEyZNG5MjWyLD47lAVqhg8YhskITQR4X71zVXo0fl5DwViQzI 11 | iaaM/BgFOc1h6KPO0QYCvXiaoABCHMRJqDDoOSdmCvSECJSW8yRJoZymupKmdKcP 12 | 4JXXDlSMZGiHEQcxV8mPE3L5CZLH7NzTtOgPUPpt9ljNQ+Ilhy0qE5fMDyNG32lq 13 | UNSigsq9/lyW+mKPjsuL9MTiOHYXTaqcwZUdNYQfo1uzVxcriXH9iRghGn7fuFQ9 14 | UW0yG3ec8kAJs58oyaDB72I4vI7n7PYb5FTK/mNiKVRBFbpveavWqNt9mp+RBxBN 15 | pOk6Yk4sQALPe/fCSSUsrovZxonm6bhG4dR2rs56q6Y/13Nox5fbkhD/ziyn7zU1 16 | cnsr71WgsHKJkrLYY5rcyXBuhkIV8B9Y/X02M7FaUup8+H5fQr+aY4eaP6lkOfun 17 | y2ZdtufF2j67TSMjAgMBAAGjUDBOMB0GA1UdDgQWBBQNl++oS2E319WipurTvGKp 18 | q3sr2jAfBgNVHSMEGDAWgBQNl++oS2E319WipurTvGKpq3sr2jAMBgNVHRMEBTAD 19 | AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBKX+ZjGOpTz5yfF80YgOJsAhZ8mKmKHB6E 20 | RrdRqAKgdrxxDq7iB4vbeyyONBHd6wpUP46h2UYw8eI3RdFRlVwGI7tmNFz7221F 21 | k3gPYDgP6HFtWOwTosoA9A93HGmfWeakYipxyDdLC+lozf+1H/qVPzFUZkc7Y5Wd 22 | 8IpRveKTq4JhaHrrkbWRBTHiMd3+XeAcF6XNoLykmBi/k3ji6SS5Ua4xss2lPDgr 23 | mhnh2J3CCJ36xZ2aHmMNHb3oHKUz5TdRDxLL5bedXi8PGRvtb0KFjD1zjBWZi+n6 24 | Wf/Cp77a70p80UVzHV7eFsWQuJ4OaSh9bBPyJxXsk6CDD99mN076n88WuEMl0Vjv 25 | UJdw3QQI0GHNRiPMjNjpN2J8C4+J1l9zVssomHHS854TFGY6omoCI1Q9iRNlPaXY 26 | eolc94ktXbebVzpK5iwyBM/GVPXZQRWT2Fc5JnKBfQzqlwquSsA0Ctqi0lgrsNxq 27 | 1mkduO5ayibrEYnGftLgMSLh08mVaZaJLBO1kyDWfyouFMB5ujEbvXqz4oER395k 28 | xjEqo2oLcOfSG/HnHHpRCzqx6fg1tImGOZ1mSqQG9bGXgT6cQk0Qtj9zgomXBAvG 29 | QIliykO6rPhm1ayAwyspKK5LY9426euXbfpBjfe6GMvD73FkBGBIo3athLOmg4W4 30 | RHst3wAbkA== 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /src/tests/assets/tls-ca-certificate.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKQIBAAKCAgEAySuicV6oFUbM5MbVpPuqh3BcYWoCWHAeI/5QYxCwipJ+Lbfh 3 | 4cT2aBickjg9GyS10TSGntUvx32cMmH9gulabtd0nZ0nmCYOuQpVIZUBvQdjeMAo 4 | ify1HuHrGQH73KaJhHBSut/VGpv+5qJQ/3LL4NXLchgy+y6f1cQp2qBKW3joAYQ3 5 | UZC9HsmSxSR+XVSAI7ldgFGXG26Qv1fpdBBRCtx1xU0AAEphSpBF+08IB64TJk0b 6 | kyNbIsPjuUBWqGDxiGyQhNBHhfvXNVejR+XkPBWJDMiJpoz8GAU5zWHoo87RBgK9 7 | eJqgAEIcxEmoMOg5J2YK9IQIlJbzJEmhnKa6kqZ0pw/gldcOVIxkaIcRBzFXyY8T 8 | cvkJksfs3NO06A9Q+m32WM1D4iWHLSoTl8wPI0bfaWpQ1KKCyr3+XJb6Yo+Oy4v0 9 | xOI4dhdNqpzBlR01hB+jW7NXFyuJcf2JGCEaft+4VD1RbTIbd5zyQAmznyjJoMHv 10 | Yji8jufs9hvkVMr+Y2IpVEEVum95q9ao232an5EHEE2k6TpiTixAAs9798JJJSyu 11 | i9nGiebpuEbh1Hauznqrpj/Xc2jHl9uSEP/OLKfvNTVyeyvvVaCwcomSsthjmtzJ 12 | cG6GQhXwH1j9fTYzsVpS6nz4fl9Cv5pjh5o/qWQ5+6fLZl2258XaPrtNIyMCAwEA 13 | AQKCAgEAgaDCXfc2q+8hXFHbnSIl8nwuqv7aYA5u/ZaESjGY8NIQyHjy7r4yYUVq 14 | rrXaekEff50vGe4ZiZyhJ74I03B2u3HOTTnVJ69uUUIdNTSFGD9Ik6iO9suGEk5V 15 | ZZnnp3kQp+yRAqX09d6LVCZHtzNLuKdLH0wEneKfT+nFZoFfKK4yIbgGxWJU2x/c 16 | mXg4jP9ESsDJRURN8HlAgGuvnQBemHcS1H4XgBhttNReo997NyeuY0HQgPGJak97 17 | 0AVrUbiWKuh5/3sOe4GaXV8JcezrMuRVmY0nVdEHKJtFcdijRdqgyhS97pcEfuYb 18 | D0i/PjSFj6Orb4Ac31KdfkaMAxtUoMHTlWcYwWey44pHkTylggLtAgDir+A85TeG 19 | bWTqyV0RdTA0tZsM98OvBDYHg+smQ1+I8Odr2hq9AaVRqI0uHQ1payQY/RwhPzEG 20 | 9Md55wXRqITSfHBfM7QcfUAFw5DB3+7SvUhCMprQUUMzvDPN0Yq7CSWB+RIC4tma 21 | D8ez1Up1SdhYkw21r0aKavizJ68iANXPJ6ASREBJyQHk98X2ew3tyionsGAT+agK 22 | 5jXKEaKJPCax1kaKN0xW1liaIzS8aTU54R/OAXaxctVWRlmQ39z3t+7K7wf3hCUD 23 | iE7vLs9qXtm/oTMKjfWMcV5RlUsEwMCDraxponu4X1F+s4abXdECggEBAPJuxSY1 24 | 0eztXFfNx4PmWByiM8CSkr1+X8jS0QHQL8qxY0CePZbwOx5KJvCz001NHDAJkFci 25 | mqWe8d43G3qfPvXkHvGBzjndV/XdQTBcDJMAMs/NN4Ul1RH2PqS8y1tijt2OFW+v 26 | dXYQUcpGplMS82vFKHE+048mpW2lLEbwCnfVizglWZkMW7rJh3JQqAasdw+b5J8R 27 | dYc1PjnS5C9NdnI5NJUwfGqn6hYaKk6KSJLW0qZncPg7XrvTCoktZpehclu1d5Qk 28 | 1pKJfUQf/JdaKm2nxeYNWTvjGaLqfX1GEWxD9v0gaeyAVT9U6KhtDvuK2yoLoNhK 29 | NBiOXUzMPO3NZRcCggEBANRtt8DR72ahE8S8rWCs3koY/CJyRh4mecMgRbp+LC/y 30 | ZX6jApC+pH2KWjOILWzQLF858RQSu2J1d5nEFzqmaoRTUR4wRhNimjTyJAKrpNbU 31 | DmJOuEVaAbzAecv7mTMLdMZp2P3EuOOo0itv0fjQF5Qn4pqOD428oZEXqNf5ul+0 32 | V51cjJedufNGQN1aryP9yU7yNkuSRiJdorkajoBIGuLRtNIEKVtUvFTWsbIRsOOY 33 | jverOHdQojJX9yHSO24OpfGRM6XXzSNNwepGprXPrNawZ9HZF61j7iQxc+rTgIFX 34 | p2zucajmoRRrvh9fssy9uvRU5WNl/yJ6ceXtyYPKkdUCggEBAI24EUnH9oLhMrUl 35 | VzeU5PdAHq65QEzD3myASKwdroJ39gRlPK3Km1SWlnLpPGGY7RxrytQVJ4AgDVYQ 36 | suCYzO5jP2+AqTSMXwocICqL9NHOMOXnGkicmBTa9T3KG7q9P7TmhUN3t1ugKJ/t 37 | cvdnQaNDauPgjT3GorgY8Ww6kHQDAlJ9CwZ8AfeMLcC2w7K7CRK32Arg4up6Rnj4 38 | /mlkiBQ3urRn1qNmq2HmQ8sL7Wbha9zY5WJHGpcDxMnyH2IOf9J6n3+blU+uTesQ 39 | I9G6ZMkbZYWZUgu/M1JYb6lW14KJrI3GcAVP9AWEtkkXD8YeIwZOK+WkYxXjy0jE 40 | r+13v1sCggEAaGq+BgOrX5ERvITZ3ElcwqNhXDU7jhpvg6BbZf4bDj5h8UuDOU7c 41 | X/EfwXDQk0oIZY5/l9RVU2JWNbkTifq/JOgnWWV1LJYtIpVbagyg7BA0OdJj9YL2 42 | k6cwtzmm5lZdQiJJTQPjtdQH6t3+IgV3cZXhZwMpVgxAhqdQkUjpeZ2V633Qhb47 43 | 5v7DZ4rpyBE5YJriYWc3HjFUiRQqs5e9z3wxG4J8Ka6PHEyj79mOT3Fv5QG+sbdq 44 | 4sAmc/N4zvJYU1OCRaS76vyPsu6jh37bgHQZtCa34rzaE+RTDDyGUA2b+ImESPeE 45 | ehszywwpQUaJnQwV7TP3oyT404J8qVFrOQKCAQBxoBfTELeqW6BrKuW/cSuwlleT 46 | 3cqD4tFEs+i9Zam7QLarW4gRumcjvM6/f11AEvTDxsOnHPlc0eOu15omJ8A7UONR 47 | /9VNJtpe6XJHF60F5uY4Vus1GBpwoPNaAjtv8Y5bgzV5ypKABOuWiB+vRAaTp3Qk 48 | n17HvNUmdpgEsQfkbWHyxChvmbVfQYMJ2iDQtkKeLnIVfJ3j9ITM5cj79BE50van 49 | OMgTeVRPrD2ys8vtxteFa8Cgg0sdvTLOyfI9ih5+iGTR/swN0GTsVze6e8gFqYu5 50 | Z6gweSmndRCPRgZkDJV4FVsROcvPaU+9luiJezY2b7vpCuyybo10OkzUR3v2 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /src/tests/assets/tls-certificate.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID3TCCAcUCCQDS8vC3eKnWPTANBgkqhkiG9w0BAQsFADA4MRYwFAYDVQQKDA1s 3 | aWJ2bW9kLXJlZGlzMR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN 4 | MjAwNTE0MDkwOTU4WhcNMzAwNTEyMDkwOTU4WjApMRYwFAYDVQQKDA1saWJ2bW9k 5 | LXJlZGlzMQ8wDQYDVQQDDAZTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 6 | ggEKAoIBAQC8dWT0rX1RtQFs7abaraAdXfhi0xGGz1hlxz41Y7HYPPHe9QZTMQuo 7 | zO+IQ3id4w71m2cBIr6xxQ9iDPJPclo+Pt/r2eEvQnyz0Ey47+I3ZFlJLvluB+xd 8 | Ytg4uPCr/IvVHL5r6fN6D9SJ+Tq9I47O52MfgYkkURMGVD1gVKF/gmwOlM1pxjlZ 9 | 0zwaZwpugv4T7UFIY9k4HcUuF92kJheBrW32Kk/GgoJ5JkPQlcmyqvs7HfeIRAM8 10 | c//IyPEq6EzgtR7P5ctEiWi18sIRw4DbAhyLHg2jH0JznyEAJnoqA7nB3ySc3ldX 11 | 3PY0KRdvUm2lyxTmiVLDUl3vVLCy2IS/AgMBAAEwDQYJKoZIhvcNAQELBQADggIB 12 | AEVJXtAP5VuDWlihut8tashLgCNUf5us3Y1Hc71bvkNG/9OYUNx5PyjliPvunTpW 13 | Zy880BrO//ld2COeCMUSqGb+ZMdVVGRR06SEOCb57nqjPrAJBiG4IZKjhbzfFAgQ 14 | Xzfci5c0PIHFNOe+f7K9KdV0GXO7GHMHoVadoRf5L0KhKCoFzCnVfnksYLeqK0rQ 15 | nErp/pD+mij9tOj75ggKug990LP8Pva1sQ+u/5yzM16lblw0faTXllzGy3SyBsNr 16 | fMjkT57FSSHJWROtODIvJibLsBKo1LwZ56lKjVrQNrzcdlXFaP0tHlZyQdH+A7qX 17 | 1bDB9f+pBHCOHNaWNQHho3xhw8cnGhb02hNQXMGAC8p8wWUCBplf06IzkXXcQTXO 18 | GLc3O6N8dElqVNlhGj9c7SxV69vInMadho6u0q5vj5gMp3k+58CxMyvLq+kByj98 19 | APGurxbei7Jd7Nl0F/m0PzPDXoSq+49wtZr/Xrw3g5AGqamIrTmIHZ4kdybTm1j5 20 | Ts4HMI0yYm3d1798C2kRlGeued72k6n34fKobBhI3YzEgv48DdqTAwNNMhL6ELed 21 | c/7saFpJ7GYUtuywVsijJFra1/qkHcqnY0D9VZQTQAmbgzJBLzB4821g63IkGCUy 22 | 2DgfhEw362qxgVU/Br6rZvOzW5gu/iaAUg7thDIllw0U 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /src/tests/assets/tls-certificate.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAvHVk9K19UbUBbO2m2q2gHV34YtMRhs9YZcc+NWOx2Dzx3vUG 3 | UzELqMzviEN4neMO9ZtnASK+scUPYgzyT3JaPj7f69nhL0J8s9BMuO/iN2RZSS75 4 | bgfsXWLYOLjwq/yL1Ry+a+nzeg/Uifk6vSOOzudjH4GJJFETBlQ9YFShf4JsDpTN 5 | acY5WdM8GmcKboL+E+1BSGPZOB3FLhfdpCYXga1t9ipPxoKCeSZD0JXJsqr7Ox33 6 | iEQDPHP/yMjxKuhM4LUez+XLRIlotfLCEcOA2wIcix4Nox9Cc58hACZ6KgO5wd8k 7 | nN5XV9z2NCkXb1JtpcsU5olSw1Jd71SwstiEvwIDAQABAoIBAHg2OeuJLsMLvpkJ 8 | DsG2tseExYfkMu3XHP/vE9NigHL6jR43FY3DXziRYMl+oFW2HDi2pAGpdBJZLPLS 9 | Z04eF0pVOOhs1qMaugjI+eeNvLKwo/N2r0xsaW04O8wSzBIoydlquFyFovVdW6mW 10 | /Mzg/ZslJalXK2+q94O2AGCOG2YFZl1Qjm3XPawH7PXGT4gPmjUtYATTN3OguRpV 11 | d98/WK8jXkitLBmQR6nGNoeBxAeBsHh7VWllJIzB4O0Z3WiASMMqH8SqlA1q6Gxx 12 | e0cn+IYwJBgl0Njus/sxdHHSzoYwtR8lJ36phbYngZusYLc056sTCvvUhXsQIxgt 13 | 3hdwUUECgYEA7igENfDS0tYEquxe1oAIegXgOPDhkZvVke63WITOaewbhw48qq9S 14 | 35A/JVoTKqwzouplEdwZkf6a0DDrmNM0NCc3XkRsk8qTuFyyVWRqkpmtLpF2qR5/ 15 | y7HTekV/V7Rl1ZSDi4f9/TM8THU54i83ljRLnnV4ZptPZR1fWSP/RWcCgYEAypQl 16 | FWNPrVXLhgxt02GWTdeTdIxmG8ViTsLzKN5vUQBedAXdZldpC7Zh0FyLH7cwYeEy 17 | Tv9o2Ci39cQijad/q0I0w0VtW0nbff2HnAXHprc/dS/yu+Z79vQf3kOo0plJXMSs 18 | 9H++NBRUriKxAxx0Th9+4vyxL90eo21/VZ1olukCgYEAvAtmv7ymiokoaFl4zL+N 19 | cePf1rYENbepG7A0nrVGUoZ2ed448sC6nAHGilSkG6aaowGWylJS8l7pmId4D1R8 20 | vM1WP52hadSjbQfsW9aM+7JR9xouanzFhW2kwL/NO9AaNFkuwOahGuLwsqJBESId 21 | LwGdz8GLTuFFAF7/4V+1+PMCgYEAvqjZkhyrE9eIurwd7XGOVesMRAgT3hVS5hAD 22 | bTaUjCcNvqL6cmTYYAaiXsmKwynVpnmdsM4f7jm8kdmsL6gyt4uTPymrt9x2cUjZ 23 | hhXGh3k2h2O+T+yoRZAUIkuJq9RLQL12jpNf/4IXBOFvuh7gs9pewOHVHdg+CtB2 24 | pKyGkGkCgYEAgviuigal+e0paZCxkVRnDm7sClG+Clg6KLh6p8vhNOoxe2GddesY 25 | dyNlUT324jflEtf2KBkYPLJ5u0i/DBvnMuI7UlVgSvjqPuMcyZS9kNFt+JKIdHjg 26 | RaV4Mj4uQOR3Q8wax/5n/xtiR3Fdt76XkKPa/MmetTvNp+0mR3Hg14U= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/tests/clustered.ASK-reply-messages.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests handling of ASK reply messages" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=cluster, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=true, 29 | max_connections=32, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=16); 34 | db.add_server("${redis_master2_ip}:${redis_master2_port}", cluster); 35 | db.add_server("${redis_master3_ip}:${redis_master3_port}", cluster); 36 | 37 | new master1 = redis.db( 38 | location="${redis_master1_ip}:${redis_master1_port}", 39 | type=master, 40 | connection_timeout=500, 41 | connection_ttl=0, 42 | command_timeout=0, 43 | max_command_retries=0, 44 | shared_connections=false, 45 | max_connections=1, 46 | password="", 47 | sickness_ttl=0, 48 | ignore_slaves=false, 49 | max_cluster_hops=0); 50 | new master2 = redis.db( 51 | location="${redis_master2_ip}:${redis_master2_port}", 52 | type=master, 53 | connection_timeout=500, 54 | connection_ttl=0, 55 | command_timeout=0, 56 | max_command_retries=0, 57 | shared_connections=false, 58 | max_connections=1, 59 | password="", 60 | sickness_ttl=0, 61 | ignore_slaves=false, 62 | max_cluster_hops=0); 63 | } 64 | 65 | sub vcl_deliver { 66 | # Set key in master1. 67 | db.command("SET"); 68 | db.push("${redis_key_in_master1}"); 69 | db.push("hello"); 70 | db.execute(true); 71 | if (db.reply_is_status()) { 72 | set resp.http.Reply-1 = db.get_status_reply(); 73 | } 74 | 75 | # Migrate key from master1 to master2. 76 | call migrate; 77 | 78 | # Get the key (should internally handle the ASK redirection). 79 | db.command("GET"); 80 | db.push("${redis_key_in_master1}"); 81 | db.execute(true); 82 | if (db.reply_is_string()) { 83 | set resp.http.Reply-2 = db.get_string_reply(); 84 | } 85 | 86 | # Stats. 87 | set resp.http.db-stats = db.stats(); 88 | set resp.http.db-servers-total = db.counter("servers.total"); 89 | set resp.http.db-connections-total = db.counter("connections.total"); 90 | set resp.http.db-commands-total = db.counter("commands.total"); 91 | set resp.http.db-cluster-discoveries-total = db.counter("cluster.discoveries.total"); 92 | set resp.http.db-cluster-replies-moved = db.counter("cluster.replies.moved"); 93 | set resp.http.db-cluster-replies-ask = db.counter("cluster.replies.ask"); 94 | } 95 | 96 | sub migrate { 97 | # Get the key's containing slot. 98 | master1.command("CLUSTER"); 99 | master1.push("KEYSLOT"); 100 | master1.push("${redis_key_in_master1}"); 101 | master1.execute(true); 102 | if (master1.reply_is_integer()) { 103 | set resp.http.Migration-Slot = master1.get_integer_reply(); 104 | } 105 | 106 | # Get the ID for the original node (master1). 107 | master1.command("CLUSTER"); 108 | master1.push("NODES"); 109 | master1.execute(true); 110 | if (master1.reply_is_string()) { 111 | set resp.http.Migration-NodeOrig = regsub(master1.get_reply(), "(?s)^(?:.+\n)?([0-9a-f]+) ${redis_master1_ip}:${redis_master1_port}(?:@\d+)? .*$", "\1"); 112 | if (resp.http.Migration-NodeOrig !~ "[0-9a-f]+") { 113 | unset resp.http.Migration-NodeOrig; 114 | } 115 | } 116 | 117 | # Get the ID for the destination node (master2). 118 | master1.command("CLUSTER"); 119 | master1.push("NODES"); 120 | master1.execute(true); 121 | if (master1.reply_is_string()) { 122 | set resp.http.Migration-NodeDest = regsub(master1.get_reply(), "(?s)^(?:.+\n)?([0-9a-f]+) ${redis_master2_ip}:${redis_master2_port}(?:@\d+)? .*$", "\1"); 123 | if (resp.http.Migration-NodeDest !~ "[0-9a-f]+") { 124 | unset resp.http.Migration-NodeDest; 125 | } 126 | } 127 | 128 | # Mark the destination node as IMPORTING. 129 | master2.command("CLUSTER"); 130 | master2.push("SETSLOT"); 131 | master2.push(resp.http.Migration-Slot); 132 | master2.push("IMPORTING"); 133 | master2.push(resp.http.Migration-NodeOrig); 134 | master2.execute(true); 135 | if (master2.reply_is_status()) { 136 | set resp.http.Migration-Reply-4 = master2.get_status_reply(); 137 | } 138 | 139 | # Mark the original node as MIGRATING. 140 | master1.command("CLUSTER"); 141 | master1.push("SETSLOT"); 142 | master1.push(resp.http.Migration-Slot); 143 | master1.push("MIGRATING"); 144 | master1.push(resp.http.Migration-NodeDest); 145 | master1.execute(true); 146 | if (master1.reply_is_status()) { 147 | set resp.http.Migration-Reply-5 = master1.get_status_reply(); 148 | } 149 | 150 | # Migrate key. 151 | master1.command("MIGRATE"); 152 | master1.push("${redis_master2_ip}"); 153 | master1.push("${redis_master2_port}"); 154 | master1.push("${redis_key_in_master1}"); 155 | master1.push("0"); 156 | master1.push("0"); 157 | master1.execute(true); 158 | if (master1.reply_is_status()) { 159 | set resp.http.Migration-Reply-6 = master1.get_status_reply(); 160 | } 161 | 162 | # Check that the original node replies with an ASK redirection. 163 | master1.command("GET"); 164 | master1.push("${redis_key_in_master1}"); 165 | master1.execute(true); 166 | if (master1.reply_is_error()) { 167 | if (master1.get_error_reply() ~ "^ASK ") { 168 | set resp.http.Migration-Reply-7 = "ASK"; 169 | } 170 | } 171 | } 172 | } -start 173 | 174 | client c1 { 175 | txreq 176 | rxresp 177 | 178 | expect resp.http.Reply-1 == "OK" 179 | 180 | expect resp.http.Migration-Slot != 181 | expect resp.http.Migration-NodeOrig != 182 | expect resp.http.Migration-NodeDest != 183 | expect resp.http.Migration-Reply-4 == "OK" 184 | expect resp.http.Migration-Reply-5 == "OK" 185 | expect resp.http.Migration-Reply-6 == "OK" 186 | expect resp.http.Migration-Reply-7 == "ASK" 187 | 188 | expect resp.http.Reply-2 == "hello" 189 | 190 | expect resp.http.db-servers-total == "9" 191 | expect resp.http.db-connections-total == "2" 192 | expect resp.http.db-commands-total == "3" 193 | expect resp.http.db-cluster-discoveries-total == "1" 194 | expect resp.http.db-cluster-replies-moved == "0" 195 | expect resp.http.db-cluster-replies-ask == "1" 196 | } -run 197 | 198 | varnish v1 -expect client_req == 1 199 | -------------------------------------------------------------------------------- /src/tests/clustered.MOVED-reply-messages.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests handling of MOVED reply messages" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=cluster, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=true, 29 | max_connections=32, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=16); 34 | db.add_server("${redis_master2_ip}:${redis_master2_port}", cluster); 35 | db.add_server("${redis_master3_ip}:${redis_master3_port}", cluster); 36 | 37 | new master1 = redis.db( 38 | location="${redis_master1_ip}:${redis_master1_port}", 39 | type=master, 40 | connection_timeout=500, 41 | connection_ttl=0, 42 | command_timeout=0, 43 | max_command_retries=0, 44 | shared_connections=false, 45 | max_connections=1, 46 | password="", 47 | sickness_ttl=0, 48 | ignore_slaves=false, 49 | max_cluster_hops=0); 50 | new master2 = redis.db( 51 | location="${redis_master2_ip}:${redis_master2_port}", 52 | type=master, 53 | connection_timeout=500, 54 | connection_ttl=0, 55 | command_timeout=0, 56 | max_command_retries=0, 57 | shared_connections=false, 58 | max_connections=1, 59 | password="", 60 | sickness_ttl=0, 61 | ignore_slaves=false, 62 | max_cluster_hops=0); 63 | } 64 | 65 | sub vcl_deliver { 66 | # Set key in master1. 67 | db.command("SET"); 68 | db.push("${redis_key_in_master1}"); 69 | db.push("hello"); 70 | db.execute(true); 71 | if (db.reply_is_status()) { 72 | set resp.http.Reply-1 = db.get_status_reply(); 73 | } 74 | 75 | # Migrate key from master1 to master2. 76 | call migrate; 77 | 78 | # Get the key (should internally handle the MOVED redirection). 79 | db.command("GET"); 80 | db.push("${redis_key_in_master1}"); 81 | db.execute(true); 82 | if (db.reply_is_string()) { 83 | set resp.http.Reply-2 = db.get_string_reply(); 84 | } 85 | 86 | # Stats. 87 | set resp.http.db-stats = db.stats(); 88 | set resp.http.db-servers-total = db.counter("servers.total"); 89 | set resp.http.db-connections-total = db.counter("connections.total"); 90 | set resp.http.db-commands-total = db.counter("commands.total"); 91 | set resp.http.db-cluster-discoveries-total = db.counter("cluster.discoveries.total"); 92 | set resp.http.db-cluster-replies-moved = db.counter("cluster.replies.moved"); 93 | set resp.http.db-cluster-replies-ask = db.counter("cluster.replies.ask"); 94 | } 95 | 96 | sub migrate { 97 | # Get the key's containing slot. 98 | master1.command("CLUSTER"); 99 | master1.push("KEYSLOT"); 100 | master1.push("${redis_key_in_master1}"); 101 | master1.execute(true); 102 | if (master1.reply_is_integer()) { 103 | set resp.http.Migration-Slot = master1.get_integer_reply(); 104 | } 105 | 106 | # Get the ID for the original node (master1). 107 | master1.command("CLUSTER"); 108 | master1.push("NODES"); 109 | master1.execute(true); 110 | if (master1.reply_is_string()) { 111 | set resp.http.Migration-NodeOrig = regsub(master1.get_reply(), "(?s)^(?:.+\n)?([0-9a-f]+) ${redis_master1_ip}:${redis_master1_port}(?:@\d+)? .*$", "\1"); 112 | if (resp.http.Migration-NodeOrig !~ "[0-9a-f]+") { 113 | unset resp.http.Migration-NodeOrig; 114 | } 115 | } 116 | 117 | # Get the ID for the destination node (master2). 118 | master1.command("CLUSTER"); 119 | master1.push("NODES"); 120 | master1.execute(true); 121 | if (master1.reply_is_string()) { 122 | set resp.http.Migration-NodeDest = regsub(master1.get_reply(), "(?s)^(?:.+\n)?([0-9a-f]+) ${redis_master2_ip}:${redis_master2_port}(?:@\d+)? .*$", "\1"); 123 | if (resp.http.Migration-NodeDest !~ "[0-9a-f]+") { 124 | unset resp.http.Migration-NodeDest; 125 | } 126 | } 127 | 128 | # Mark the destination node as IMPORTING. 129 | master2.command("CLUSTER"); 130 | master2.push("SETSLOT"); 131 | master2.push(resp.http.Migration-Slot); 132 | master2.push("IMPORTING"); 133 | master2.push(resp.http.Migration-NodeOrig); 134 | master2.execute(true); 135 | if (master2.reply_is_status()) { 136 | set resp.http.Migration-Reply-4 = master2.get_status_reply(); 137 | } 138 | 139 | # Mark the original node as MIGRATING. 140 | master1.command("CLUSTER"); 141 | master1.push("SETSLOT"); 142 | master1.push(resp.http.Migration-Slot); 143 | master1.push("MIGRATING"); 144 | master1.push(resp.http.Migration-NodeDest); 145 | master1.execute(true); 146 | if (master1.reply_is_status()) { 147 | set resp.http.Migration-Reply-5 = master1.get_status_reply(); 148 | } 149 | 150 | # Migrate key. 151 | master1.command("MIGRATE"); 152 | master1.push("${redis_master2_ip}"); 153 | master1.push("${redis_master2_port}"); 154 | master1.push("${redis_key_in_master1}"); 155 | master1.push("0"); 156 | master1.push("0"); 157 | master1.execute(true); 158 | if (master1.reply_is_status()) { 159 | set resp.http.Migration-Reply-6 = master1.get_status_reply(); 160 | } 161 | 162 | # Assign the containing slot to the destination node in both nodes. 163 | 164 | master1.command("CLUSTER"); 165 | master1.push("SETSLOT"); 166 | master1.push(resp.http.Migration-Slot); 167 | master1.push("NODE"); 168 | master1.push(resp.http.Migration-NodeDest); 169 | master1.execute(true); 170 | if (master1.reply_is_status()) { 171 | set resp.http.Migration-Reply-7 = master1.get_status_reply(); 172 | } 173 | 174 | master2.command("CLUSTER"); 175 | master2.push("SETSLOT"); 176 | master2.push(resp.http.Migration-Slot); 177 | master2.push("NODE"); 178 | master2.push(resp.http.Migration-NodeDest); 179 | master2.execute(true); 180 | if (master2.reply_is_status()) { 181 | set resp.http.Migration-Reply-8 = master2.get_status_reply(); 182 | } 183 | 184 | # Check that the original node replies with a MOVED redirection. 185 | master1.command("GET"); 186 | master1.push("${redis_key_in_master1}"); 187 | master1.execute(true); 188 | if (master1.reply_is_error()) { 189 | if (master1.get_error_reply() ~ "^MOVED ") { 190 | set resp.http.Migration-Reply-9 = "MOVED"; 191 | } 192 | } 193 | } 194 | } -start 195 | 196 | client c1 { 197 | txreq 198 | rxresp 199 | 200 | expect resp.http.Reply-1 == "OK" 201 | 202 | expect resp.http.Migration-Slot != 203 | expect resp.http.Migration-NodeOrig != 204 | expect resp.http.Migration-NodeDest != 205 | expect resp.http.Migration-Reply-4 == "OK" 206 | expect resp.http.Migration-Reply-5 == "OK" 207 | expect resp.http.Migration-Reply-6 == "OK" 208 | expect resp.http.Migration-Reply-7 == "OK" 209 | expect resp.http.Migration-Reply-8 == "OK" 210 | expect resp.http.Migration-Reply-9 == "MOVED" 211 | 212 | expect resp.http.Reply-2 == "hello" 213 | 214 | expect resp.http.db-servers-total == "9" 215 | expect resp.http.db-connections-total == "2" 216 | expect resp.http.db-commands-total == "3" 217 | expect resp.http.db-cluster-discoveries-total == "2" 218 | expect resp.http.db-cluster-replies-moved == "1" 219 | expect resp.http.db-cluster-replies-ask == "0" 220 | } -run 221 | 222 | varnish v1 -expect client_req == 1 223 | -------------------------------------------------------------------------------- /src/tests/clustered.basics-using-private-pool.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests basics using private pool" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=cluster, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=3, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=16); 34 | db.add_server("${redis_master2_ip}:${redis_master2_port}", cluster); 35 | db.add_server("${redis_master3_ip}:${redis_master3_port}", cluster); 36 | } 37 | 38 | sub vcl_deliver { 39 | # SET. 40 | db.command("SET"); 41 | db.push("${redis_key_in_master1}"); 42 | db.push("hello"); 43 | db.execute(true); 44 | if (db.reply_is_status()) { 45 | set resp.http.Reply-1 = db.get_status_reply(); 46 | } 47 | 48 | # SETEX. 49 | db.command("SETEX"); 50 | db.push("${redis_key_in_master2}"); 51 | db.push("3600"); 52 | db.push("Hello world!"); 53 | db.execute(true); 54 | if (db.reply_is_status()) { 55 | set resp.http.Reply-2 = db.get_status_reply(); 56 | } 57 | 58 | # GET. 59 | db.command("GET"); 60 | db.push("${redis_key_in_master2}"); 61 | db.execute(true); 62 | if (db.reply_is_string()) { 63 | set resp.http.Reply-3 = db.get_string_reply(); 64 | } 65 | 66 | # DEL. 67 | db.command("DEL"); 68 | db.push("${redis_key_in_master2}"); 69 | db.execute(true); 70 | if (db.reply_is_integer()) { 71 | set resp.http.Reply-4 = db.get_integer_reply(); 72 | } 73 | 74 | # MGET. 75 | db.command("MGET"); 76 | db.push("${redis_key_in_master1}"); 77 | db.push("{${redis_key_in_master1}}:other"); 78 | db.execute(true); 79 | if (db.reply_is_array()) { 80 | set resp.http.Reply-5-Length = db.get_array_reply_length(); 81 | set resp.http.Reply-5-Value-1 = db.get_array_reply_value(0); 82 | set resp.http.Reply-5-Value-2 = db.get_array_reply_value(1); 83 | } 84 | 85 | # MGET (CROSSSLOT). 86 | db.command("MGET"); 87 | db.push("${redis_key_in_master1}"); 88 | db.push("${redis_key_in_master2}"); 89 | db.execute(true); 90 | if (db.reply_is_error()) { 91 | set resp.http.Reply-6 = "1"; 92 | } 93 | 94 | # HMSET. 95 | db.command("HMSET"); 96 | db.push("${redis_key_in_master2}"); 97 | db.push("field1"); 98 | db.push("Hello world!"); 99 | db.push("field2"); 100 | db.push("42"); 101 | db.execute(true); 102 | if (db.reply_is_status()) { 103 | set resp.http.Reply-7 = db.get_status_reply(); 104 | } 105 | 106 | # HMGET. 107 | db.command("HGET"); 108 | db.push("${redis_key_in_master2}"); 109 | db.push("field1"); 110 | db.execute(true); 111 | if (db.reply_is_string()) { 112 | set resp.http.Reply-8 = db.get_string_reply(); 113 | } 114 | 115 | # INCR. 116 | db.command("INCR"); 117 | db.push("${redis_key_in_master1}"); 118 | db.execute(true); 119 | if (db.reply_is_error()) { 120 | set resp.http.Reply-9 = db.get_error_reply(); 121 | } 122 | 123 | # EVAL. 124 | set req.http.Script = {" 125 | redis.call('SET', KEYS[1], ARGV[1]) 126 | redis.call('SET', KEYS[2], ARGV[1]) 127 | "}; 128 | db.command("EVAL"); 129 | db.push(req.http.Script); 130 | db.push("2"); 131 | db.push("${redis_key_in_master1}"); 132 | db.push("{${redis_key_in_master1}}:other"); 133 | db.push("Atomic!"); 134 | db.execute(true); 135 | if (db.reply_is_nil()) { 136 | set resp.http.Reply-10 = "o/"; 137 | } 138 | 139 | # EVAL. 140 | db.command("EVAL"); 141 | db.push(req.http.Script); 142 | db.push("2"); 143 | db.push("${redis_key_in_master1}"); 144 | db.push("{${redis_key_in_master1}}:other"); 145 | db.push("Atomic x 2!"); 146 | db.execute(true); 147 | if (db.reply_is_nil()) { 148 | set resp.http.Reply-11 = "o/"; 149 | } 150 | 151 | # EVAL (CROSSSLOT). 152 | set req.http.Script = {" 153 | redis.call('SET', KEYS[1], ARGV[1]) 154 | redis.call('SET', KEYS[2], ARGV[1]) 155 | "}; 156 | db.command("EVAL"); 157 | db.push(req.http.Script); 158 | db.push("2"); 159 | db.push("${redis_key_in_master1}"); 160 | db.push("${redis_key_in_master2}"); 161 | db.push("Atomic!"); 162 | db.execute(true); 163 | if (db.reply_is_error()) { 164 | set resp.http.Reply-12 = "1"; 165 | } 166 | 167 | # GET. 168 | db.command("GET"); 169 | db.push("${redis_key_in_master1}"); 170 | db.execute(true); 171 | if (db.reply_is_string()) { 172 | set resp.http.Reply-13 = db.get_string_reply(); 173 | } 174 | 175 | # Stats. 176 | set resp.http.db-stats = db.stats(); 177 | set resp.http.db-servers-total = db.counter("servers.total"); 178 | set resp.http.db-connections-total = db.counter("connections.total"); 179 | set resp.http.db-commands-total = db.counter("commands.total"); 180 | set resp.http.db-commands-error = db.counter("commands.error"); 181 | set resp.http.db-commands-noscript = db.counter("commands.noscript"); 182 | set resp.http.db-cluster-discoveries-total = db.counter("cluster.discoveries.total"); 183 | set resp.http.db-cluster-replies-moved = db.counter("cluster.replies.moved"); 184 | set resp.http.db-cluster-replies-ask = db.counter("cluster.replies.ask"); 185 | } 186 | } -start 187 | 188 | # Needed for testing purposes, to store scripts in HTTP headers. 189 | varnish v1 -cliok "param.set feature -validate_headers" 190 | 191 | client c1 { 192 | txreq 193 | rxresp 194 | 195 | expect resp.http.Reply-1 == "OK" 196 | 197 | expect resp.http.Reply-2 == "OK" 198 | 199 | expect resp.http.Reply-3 == "Hello world!" 200 | 201 | expect resp.http.Reply-4 == "1" 202 | 203 | expect resp.http.Reply-5-Length == "2" 204 | expect resp.http.Reply-5-Value-1 == "hello" 205 | expect resp.http.Reply-5-Value-2 == "" 206 | 207 | expect resp.http.Reply-6 == "1" 208 | 209 | expect resp.http.Reply-7 == "OK" 210 | 211 | expect resp.http.Reply-8 == "Hello world!" 212 | 213 | expect resp.http.Reply-9 != "" 214 | 215 | expect resp.http.Reply-10 == "o/" 216 | 217 | expect resp.http.Reply-11 == "o/" 218 | 219 | expect resp.http.Reply-12 == "1" 220 | 221 | expect resp.http.Reply-13 == "Atomic x 2!" 222 | 223 | expect resp.http.db-servers-total == "9" 224 | expect resp.http.db-connections-total == "2" 225 | expect resp.http.db-commands-total == "13" 226 | expect resp.http.db-commands-error == "3" 227 | expect resp.http.db-commands-noscript == "1" 228 | expect resp.http.db-cluster-discoveries-total == "1" 229 | expect resp.http.db-cluster-replies-moved == "0" 230 | expect resp.http.db-cluster-replies-ask == "0" 231 | } -run 232 | 233 | varnish v1 -expect client_req == 1 234 | -------------------------------------------------------------------------------- /src/tests/clustered.basics-using-shared-pool.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests basics using shared pool" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=cluster, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=true, 29 | max_connections=32, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=16); 34 | db.add_server("${redis_master2_ip}:${redis_master2_port}", cluster); 35 | db.add_server("${redis_master3_ip}:${redis_master3_port}", cluster); 36 | } 37 | 38 | sub vcl_deliver { 39 | # SET. 40 | db.command("SET"); 41 | db.push("${redis_key_in_master1}"); 42 | db.push("hello"); 43 | db.execute(true); 44 | if (db.reply_is_status()) { 45 | set resp.http.Reply-1 = db.get_status_reply(); 46 | } 47 | 48 | # SETEX. 49 | db.command("SETEX"); 50 | db.push("${redis_key_in_master2}"); 51 | db.push("3600"); 52 | db.push("Hello world!"); 53 | db.execute(true); 54 | if (db.reply_is_status()) { 55 | set resp.http.Reply-2 = db.get_status_reply(); 56 | } 57 | 58 | # GET. 59 | db.command("GET"); 60 | db.push("${redis_key_in_master2}"); 61 | db.execute(true); 62 | if (db.reply_is_string()) { 63 | set resp.http.Reply-3 = db.get_string_reply(); 64 | } 65 | 66 | # DEL. 67 | db.command("DEL"); 68 | db.push("${redis_key_in_master2}"); 69 | db.execute(true); 70 | if (db.reply_is_integer()) { 71 | set resp.http.Reply-4 = db.get_integer_reply(); 72 | } 73 | 74 | # MGET. 75 | db.command("MGET"); 76 | db.push("${redis_key_in_master1}"); 77 | db.push("{${redis_key_in_master1}}:other"); 78 | db.execute(true); 79 | if (db.reply_is_array()) { 80 | set resp.http.Reply-5-Length = db.get_array_reply_length(); 81 | set resp.http.Reply-5-Value-1 = db.get_array_reply_value(0); 82 | set resp.http.Reply-5-Value-2 = db.get_array_reply_value(1); 83 | } 84 | 85 | # MGET (CROSSSLOT). 86 | db.command("MGET"); 87 | db.push("${redis_key_in_master1}"); 88 | db.push("${redis_key_in_master2}"); 89 | db.execute(true); 90 | if (db.reply_is_error()) { 91 | set resp.http.Reply-6 = "1"; 92 | } 93 | 94 | # HMSET. 95 | db.command("HMSET"); 96 | db.push("${redis_key_in_master2}"); 97 | db.push("field1"); 98 | db.push("Hello world!"); 99 | db.push("field2"); 100 | db.push("42"); 101 | db.execute(true); 102 | if (db.reply_is_status()) { 103 | set resp.http.Reply-7 = db.get_status_reply(); 104 | } 105 | 106 | # HMGET. 107 | db.command("HGET"); 108 | db.push("${redis_key_in_master2}"); 109 | db.push("field1"); 110 | db.execute(true); 111 | if (db.reply_is_string()) { 112 | set resp.http.Reply-8 = db.get_string_reply(); 113 | } 114 | 115 | # INCR. 116 | db.command("INCR"); 117 | db.push("${redis_key_in_master1}"); 118 | db.execute(true); 119 | if (db.reply_is_error()) { 120 | set resp.http.Reply-9 = db.get_error_reply(); 121 | } 122 | 123 | # EVAL. 124 | set req.http.Script = {" 125 | redis.call('SET', KEYS[1], ARGV[1]) 126 | redis.call('SET', KEYS[2], ARGV[1]) 127 | "}; 128 | db.command("EVAL"); 129 | db.push(req.http.Script); 130 | db.push("2"); 131 | db.push("${redis_key_in_master1}"); 132 | db.push("{${redis_key_in_master1}}:other"); 133 | db.push("Atomic!"); 134 | db.execute(true); 135 | if (db.reply_is_nil()) { 136 | set resp.http.Reply-10 = "o/"; 137 | } 138 | 139 | # EVAL. 140 | db.command("EVAL"); 141 | db.push(req.http.Script); 142 | db.push("2"); 143 | db.push("${redis_key_in_master1}"); 144 | db.push("{${redis_key_in_master1}}:other"); 145 | db.push("Atomic x 2!"); 146 | db.execute(true); 147 | if (db.reply_is_nil()) { 148 | set resp.http.Reply-11 = "o/"; 149 | } 150 | 151 | # EVAL (CROSSSLOT). 152 | set req.http.Script = {" 153 | redis.call('SET', KEYS[1], ARGV[1]) 154 | redis.call('SET', KEYS[2], ARGV[1]) 155 | "}; 156 | db.command("EVAL"); 157 | db.push(req.http.Script); 158 | db.push("2"); 159 | db.push("${redis_key_in_master1}"); 160 | db.push("${redis_key_in_master2}"); 161 | db.push("Atomic!"); 162 | db.execute(true); 163 | if (db.reply_is_error()) { 164 | set resp.http.Reply-12 = "1"; 165 | } 166 | 167 | # GET. 168 | db.command("GET"); 169 | db.push("${redis_key_in_master1}"); 170 | db.execute(true); 171 | if (db.reply_is_string()) { 172 | set resp.http.Reply-13 = db.get_string_reply(); 173 | } 174 | 175 | # Stats. 176 | set resp.http.db-stats = db.stats(); 177 | set resp.http.db-servers-total = db.counter("servers.total"); 178 | set resp.http.db-connections-total = db.counter("connections.total"); 179 | set resp.http.db-commands-total = db.counter("commands.total"); 180 | set resp.http.db-commands-error = db.counter("commands.error"); 181 | set resp.http.db-commands-noscript = db.counter("commands.noscript"); 182 | set resp.http.db-cluster-discoveries-total = db.counter("cluster.discoveries.total"); 183 | set resp.http.db-cluster-replies-moved = db.counter("cluster.replies.moved"); 184 | set resp.http.db-cluster-replies-ask = db.counter("cluster.replies.ask"); 185 | } 186 | } -start 187 | 188 | # Needed for testing purposes, to store scripts in HTTP headers. 189 | varnish v1 -cliok "param.set feature -validate_headers" 190 | 191 | client c1 { 192 | txreq 193 | rxresp 194 | 195 | expect resp.http.Reply-1 == "OK" 196 | 197 | expect resp.http.Reply-2 == "OK" 198 | 199 | expect resp.http.Reply-3 == "Hello world!" 200 | 201 | expect resp.http.Reply-4 == "1" 202 | 203 | expect resp.http.Reply-5-Length == "2" 204 | expect resp.http.Reply-5-Value-1 == "hello" 205 | expect resp.http.Reply-5-Value-2 == "" 206 | 207 | expect resp.http.Reply-6 == "1" 208 | 209 | expect resp.http.Reply-7 == "OK" 210 | 211 | expect resp.http.Reply-8 == "Hello world!" 212 | 213 | expect resp.http.Reply-9 != "" 214 | 215 | expect resp.http.Reply-10 == "o/" 216 | 217 | expect resp.http.Reply-11 == "o/" 218 | 219 | expect resp.http.Reply-12 == "1" 220 | 221 | expect resp.http.Reply-13 == "Atomic x 2!" 222 | 223 | expect resp.http.db-servers-total == "9" 224 | expect resp.http.db-connections-total == "2" 225 | expect resp.http.db-commands-total == "13" 226 | expect resp.http.db-commands-error == "3" 227 | expect resp.http.db-commands-noscript == "1" 228 | expect resp.http.db-cluster-discoveries-total == "1" 229 | expect resp.http.db-cluster-replies-moved == "0" 230 | expect resp.http.db-cluster-replies-ask == "0" 231 | } -run 232 | 233 | varnish v1 -expect client_req == 1 234 | -------------------------------------------------------------------------------- /src/tests/clustered.template.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Minimal test template" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=cluster, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=true, 29 | max_connections=32, 30 | password="", 31 | sickness_ttl=0, 32 | max_cluster_hops=16); 33 | db.add_server("${redis_master2_ip}:${redis_master2_port}", cluster); 34 | db.add_server("${redis_master3_ip}:${redis_master3_port}", cluster); 35 | } 36 | 37 | sub vcl_deliver { 38 | # Stats. 39 | set resp.http.db-stats = db.stats(); 40 | set resp.http.db-servers-total = db.counter("servers.total"); 41 | set resp.http.db-connections-total = db.counter("connections.total"); 42 | set resp.http.db-commands-total = db.counter("commands.total"); 43 | set resp.http.db-cluster-discoveries-total = db.counter("cluster.discoveries.total"); 44 | set resp.http.db-cluster-replies-moved = db.counter("cluster.replies.moved"); 45 | set resp.http.db-cluster-replies-ask = db.counter("cluster.replies.ask"); 46 | } 47 | } -start 48 | 49 | client c1 { 50 | txreq 51 | rxresp 52 | 53 | expect resp.http.db-servers-total == "9" 54 | expect resp.http.db-connections-total == "0" 55 | expect resp.http.db-commands-total == "0" 56 | expect resp.http.db-cluster-discoveries-total == "1" 57 | expect resp.http.db-cluster-replies-moved == "0" 58 | expect resp.http.db-cluster-replies-ask == "0" 59 | } -run 60 | 61 | varnish v1 -expect client_req == 1 62 | -------------------------------------------------------------------------------- /src/tests/runner.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | ## 4 | ## Configuration. 5 | ## 6 | IPV6=0 7 | 8 | DB_STANDALONE_MASTER_SERVERS=2 9 | DB_STANDALONE_SLAVE_SERVERS=2 10 | DB_STANDALONE_SENTINEL_SERVERS=3 11 | DB_STANDALONE_START_PORT=40000 12 | DB_CLUSTER_SERVERS=9 13 | DB_CLUSTER_REPLICAS=2 14 | DB_CLUSTER_START_PORT=50000 15 | 16 | ## 17 | ## Cleanup callback. 18 | ## 19 | cleanup() { 20 | set +x 21 | 22 | for MASTER_INDEX in $(seq 1 $DB_STANDALONE_MASTER_SERVERS); do 23 | if [[ -s "$1/db-master$MASTER_INDEX.pid" ]]; then 24 | kill -9 $(cat "$1/db-master$MASTER_INDEX.pid") 25 | fi 26 | for SLAVE_INDEX in $(seq 1 $DB_STANDALONE_SLAVE_SERVERS); do 27 | if [[ -s "$1/db-slave${MASTER_INDEX}_$SLAVE_INDEX.pid" ]]; then 28 | kill -9 $(cat "$1/db-slave${MASTER_INDEX}_$SLAVE_INDEX.pid") 29 | fi 30 | done 31 | done 32 | 33 | for INDEX in $(seq 1 $DB_STANDALONE_SENTINEL_SERVERS); do 34 | if [[ -s "$1/db-sentinel$INDEX.pid" ]]; then 35 | kill -9 $(cat "$1/db-sentinel$INDEX.pid") 36 | fi 37 | done 38 | 39 | for INDEX in $(seq 1 $DB_CLUSTER_SERVERS); do 40 | if [[ -s "$1/db-server$INDEX.pid" ]]; then 41 | kill -9 $(cat "$1/db-server$INDEX.pid") 42 | fi 43 | done 44 | 45 | rm -rf "$1" 46 | echo 47 | } 48 | 49 | ## 50 | ## Initializations. 51 | ## 52 | set -e 53 | ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 54 | TMP=`mktemp -d` 55 | chmod o+rwx "$TMP" 56 | CONTEXT="" 57 | 58 | ## 59 | ## Register cleanup callback. 60 | ## 61 | trap "cleanup $TMP" EXIT 62 | 63 | ## 64 | ## Check CLI is available & get DB version. Fail test if DB is not available. 65 | ## 66 | if [ -x "$(command -v redis-cli)" ]; then 67 | VERSION=$(redis-cli --version | sed "s/^redis-cli \([^ ]*\).*$/\1/" | awk -F. '{ printf("%d%03d%03d\n", $1, $2, $3) }') 68 | CONTEXT="\ 69 | $CONTEXT \ 70 | -Dredis_version=$VERSION \ 71 | -Dredis_tls_cafile=$ROOT/assets/tls-ca-certificate.crt \ 72 | -Dredis_tls_certfile=$ROOT/assets/tls-certificate.crt \ 73 | -Dredis_tls_keyfile=$ROOT/assets/tls-certificate.key" 74 | else 75 | echo 'Database not found!' 76 | exit 1 77 | fi 78 | 79 | ## 80 | ## Silently pass test if minimum DB version is not fulfilled. 81 | ## 82 | if [[ ${@: -1} =~ ^.*\.([0-9]{7,})\.[^\.]*\.vtc(\.disabled)?$ ]]; then 83 | if [ "$VERSION" -lt "${BASH_REMATCH[1]}" ]; then 84 | echo 'Test silently skipped! A newer DB version is needed.' 85 | exit 0 86 | fi 87 | fi 88 | 89 | ## 90 | ## Launch standalone DB servers? 91 | ## 92 | if [[ ${@: -1} =~ ^.*standalone(\.[0-9]{7,})?\.[^\.]*\.vtc(\.disabled)?$ ]]; then 93 | for MASTER_INDEX in $(seq 1 $DB_STANDALONE_MASTER_SERVERS); do 94 | [[ $IPV6 = 1 ]] && MASTER_IP=::1 || MASTER_IP=127.0.0.$MASTER_INDEX 95 | MASTER_PORT=$((DB_STANDALONE_START_PORT+MASTER_INDEX)) 96 | MASTER_TLS_PORT=$((MASTER_PORT+1000)) 97 | cat > "$TMP/db-master$MASTER_INDEX.conf" <> "$TMP/db-master$MASTER_INDEX.conf" <> "$TMP/db-master$MASTER_INDEX.conf" < "$TMP/db-slave${MASTER_INDEX}_$SLAVE_INDEX.conf" <> "$TMP/db-slave${MASTER_INDEX}_$SLAVE_INDEX.conf" <> "$TMP/db-sentinel$SENTINEL_INDEX.conf" <> "$TMP/db-sentinel$INDEX.conf" <> "$TMP/db-sentinel$INDEX.conf" < "$TMP/db-server$INDEX.conf" <> "$TMP/db-server$INDEX.conf" < /dev/null 240 | else 241 | yes yes | redis-trib.rb create --replicas $DB_CLUSTER_REPLICAS $SERVERS > /dev/null 242 | fi 243 | 244 | # Wait for cluster formation in a rudementary way. 245 | [[ $IPV6 = 1 ]] && HOST=::1 || HOST=127.0.0.1 246 | [[ $IPV6 = 1 ]] && PATTERN=::1 || PATTERN=127[.]0[.]0[.] 247 | while [ $(redis-cli -h $HOST -p $((DB_CLUSTER_START_PORT+1)) CLUSTER SLOTS | grep "$PATTERN" | wc -l) -lt $DB_CLUSTER_SERVERS ]; do 248 | sleep 1 249 | done 250 | sleep 1 251 | 252 | # Add to context: 253 | # - All master nodes' addresses ordered by the slots they handle 254 | # (redis_master1, redis_master2, ...). 255 | # - An example key for each of those master nodes (redis_key_in_master1, 256 | # redis_key_in_master2, ...). 257 | INDEX=1 258 | while read LINE; do 259 | CONTEXT="\ 260 | $CONTEXT \ 261 | -Dredis_master${INDEX}_ip=$(echo $LINE | cut -f 2 -d ' ' | cut -f 1 -d '@' | rev | cut -f 2- -d ':' | rev) \ 262 | -Dredis_master${INDEX}_port=$(echo $LINE | cut -f 2 -d ' ' | cut -f 1 -d '@' | rev | cut -f 1 -d ':' | rev) \ 263 | -Dredis_key_in_master${INDEX}=$(grep "^$(echo $LINE | cut -f 9 -d ' ' | cut -f 1 -d '-'): " $ROOT/assets/hashslot-keys.txt | cut -f 2 -d ' ')" 264 | INDEX=$(( INDEX + 1 )) 265 | done <<< "$(redis-cli -h $HOST -p $((DB_CLUSTER_START_PORT+1)) CLUSTER NODES | grep master | sort -k 9 -n)" 266 | fi 267 | 268 | ## 269 | ## Execute wrapped command? 270 | ## 271 | set -x 272 | "$1" $CONTEXT "${@:2}" 273 | -------------------------------------------------------------------------------- /src/tests/standalone.6000000.TLS.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests TLS support" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_tls_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=1, 30 | tls=true, 31 | tls_cafile="${redis_tls_cafile}", 32 | tls_certfile="${redis_tls_certfile}", 33 | tls_keyfile="${redis_tls_keyfile}", 34 | password="", 35 | sickness_ttl=0, 36 | ignore_slaves=false, 37 | max_cluster_hops=0); 38 | } 39 | 40 | sub vcl_deliver { 41 | # SET. 42 | db.command("SET"); 43 | db.push("foo"); 44 | db.push("Hello world!"); 45 | db.execute(true); 46 | if (db.reply_is_status()) { 47 | set resp.http.Reply-1 = db.get_status_reply(); 48 | } 49 | 50 | # GET. 51 | db.command("GET"); 52 | db.push("foo"); 53 | db.execute(true); 54 | if (db.reply_is_string()) { 55 | set resp.http.Reply-2 = db.get_string_reply(); 56 | } 57 | 58 | # Stats. 59 | set resp.http.db-stats = db.stats(); 60 | set resp.http.db-servers-total = db.counter("servers.total"); 61 | set resp.http.db-connections-total = db.counter("connections.total"); 62 | set resp.http.db-connections-dropped-error = db.counter("connections.dropped.error"); 63 | set resp.http.db-commands-total = db.counter("commands.total"); 64 | set resp.http.db-commands-error = db.counter("commands.error"); 65 | set resp.http.db-commands-failed = db.counter("commands.failed"); 66 | set resp.http.db-commands-noscript = db.counter("commands.noscript"); 67 | } 68 | } -start 69 | 70 | client c1 { 71 | txreq 72 | rxresp 73 | 74 | expect resp.http.Reply-1 == "OK" 75 | 76 | expect resp.http.Reply-2 == "Hello world!" 77 | 78 | expect resp.http.db-servers-total == "1" 79 | expect resp.http.db-connections-total == "1" 80 | expect resp.http.db-connections-dropped-error == "0" 81 | expect resp.http.db-commands-total == "2" 82 | expect resp.http.db-commands-failed == "0" 83 | expect resp.http.db-commands-error == "0" 84 | expect resp.http.db-commands-noscript == "0" 85 | } -run 86 | 87 | varnish v1 -expect client_req == 1 88 | -------------------------------------------------------------------------------- /src/tests/standalone.6000000.sentinels.vtc.disabled: -------------------------------------------------------------------------------- 1 | varnishtest "Tests Sentinels + TLS + RESP3." 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 3 -start 7 | 8 | # Let Sentinels discover all master and slave servers. 9 | delay 15 10 | 11 | # Varnish configured with no password to connect to sentinels. 12 | varnish v1 -vcl+backend { 13 | import ${vmod_redis}; 14 | 15 | sub vcl_init { 16 | redis.subnets( 17 | masks={""}); 18 | 19 | redis.sentinels( 20 | locations={" 21 | ${redis_sentinel1_ip}:${redis_sentinel1_tls_port}, 22 | ${redis_sentinel2_ip}:${redis_sentinel2_tls_port}, 23 | ${redis_sentinel3_ip}:${redis_sentinel3_tls_port} 24 | "}, 25 | period=3, 26 | connection_timeout=500, 27 | command_timeout=0, 28 | protocol=RESP3, 29 | tls=true, 30 | tls_cafile="${redis_tls_cafile}", 31 | tls_certfile="${redis_tls_certfile}", 32 | tls_keyfile="${redis_tls_keyfile}"); 33 | } 34 | } -start 35 | 36 | # Varnish configured with an incorrect password to connect to sentinels. 37 | varnish v2 -vcl+backend { 38 | import ${vmod_redis}; 39 | 40 | sub vcl_init { 41 | redis.subnets( 42 | masks={""}); 43 | 44 | redis.sentinels( 45 | locations={" 46 | ${redis_sentinel1_ip}:${redis_sentinel1_tls_port}, 47 | ${redis_sentinel2_ip}:${redis_sentinel2_tls_port}, 48 | ${redis_sentinel3_ip}:${redis_sentinel3_tls_port} 49 | "}, 50 | period=3, 51 | connection_timeout=500, 52 | command_timeout=0, 53 | protocol=RESP3, 54 | tls=true, 55 | tls_cafile="${redis_tls_cafile}", 56 | tls_certfile="${redis_tls_certfile}", 57 | tls_keyfile="${redis_tls_keyfile}", 58 | password="wrong-password"); 59 | } 60 | } -start 61 | 62 | # Varnish configured with a correct password to connect to sentinels. 63 | varnish v3 -vcl+backend { 64 | import ${vmod_redis}; 65 | 66 | sub vcl_init { 67 | redis.subnets( 68 | masks={""}); 69 | 70 | redis.sentinels( 71 | locations={" 72 | ${redis_sentinel1_ip}:${redis_sentinel1_tls_port}, 73 | ${redis_sentinel2_ip}:${redis_sentinel2_tls_port}, 74 | ${redis_sentinel3_ip}:${redis_sentinel3_tls_port} 75 | "}, 76 | period=3, 77 | connection_timeout=500, 78 | command_timeout=0, 79 | protocol=RESP3, 80 | tls=true, 81 | tls_cafile="${redis_tls_cafile}", 82 | tls_certfile="${redis_tls_certfile}", 83 | tls_keyfile="${redis_tls_keyfile}", 84 | password="s3cr3t"); 85 | 86 | new db = redis.db( 87 | location="${redis_master1_ip}:${redis_master1_tls_port}", 88 | type=auto, 89 | connection_timeout=500, 90 | connection_ttl=0, 91 | command_timeout=0, 92 | max_command_retries=0, 93 | shared_connections=false, 94 | max_connections=2, 95 | protocol=RESP3, 96 | tls=true, 97 | tls_cafile="${redis_tls_cafile}", 98 | tls_certfile="${redis_tls_certfile}", 99 | tls_keyfile="${redis_tls_keyfile}", 100 | password="", 101 | sickness_ttl=0, 102 | ignore_slaves=false, 103 | max_cluster_hops=0); 104 | db.add_server("${redis_slave1_1_ip}:${redis_slave1_1_tls_port}", auto); 105 | db.add_server("${redis_slave1_2_ip}:${redis_slave1_2_tls_port}", auto); 106 | } 107 | 108 | sub vcl_deliver { 109 | # Simulate unreachable master. 110 | db.command("DEBUG"); 111 | db.push("sleep"); 112 | db.push("20"); 113 | db.execute(true); 114 | 115 | # Stats. 116 | set resp.http.db-stats = db.stats(); 117 | } 118 | } -start 119 | 120 | delay 1 121 | 122 | logexpect l1 -v v1 -g raw 123 | 124 | client c1 -connect ${v1_sock} { 125 | txreq 126 | rxresp 127 | } -run 128 | 129 | logexpect l1 { 130 | expect * 0 VCL_Error {^\[REDIS\]\[helloCallback:.*Failed to negotiate protocol in Sentinel connection.*NOAUTH.*} 131 | expect * 0 VCL_Error {^\[REDIS\]\[disconnectCallback:.*Sentinel connection lost.*NOAUTH.*} 132 | expect * 0 VCL_Error {^\[REDIS\]\[discover_servers:.*Failed to execute Sentinel HELLO command.*NOAUTH.*} 133 | } -start 134 | 135 | logexpect l1 -wait 136 | 137 | logexpect l2 -v v2 -g raw 138 | 139 | client c2 -connect ${v2_sock} { 140 | txreq 141 | rxresp 142 | } -run 143 | 144 | logexpect l2 { 145 | expect * 0 VCL_Error {^\[REDIS\]\[authorizeCallback:.*Failed to authenticate Sentinel connection.*WRONGPASS.*} 146 | expect * 0 VCL_Error {^\[REDIS\]\[disconnectCallback:.*Sentinel connection lost.*NOAUTH.*} 147 | expect * 0 VCL_Error {^\[REDIS\]\[discover_servers:.*Failed to execute Sentinel AUTH command.*WRONGPASS.*} 148 | } -start 149 | 150 | logexpect l2 -wait 151 | 152 | client c3 -connect ${v3_sock} { 153 | txreq 154 | rxresp 155 | } -run 156 | 157 | delay 5 158 | 159 | # XXX: not really an useful test at the moment. 160 | 161 | varnish v1 -expect client_req == 1 162 | varnish v2 -expect client_req == 1 163 | varnish v3 -expect client_req == 1 164 | -------------------------------------------------------------------------------- /src/tests/standalone.VCL-temperature-and-discard.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test VCL temperature and discard." 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db1 = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | 35 | new db2 = redis.db( 36 | location="${redis_master2_ip}:${redis_master2_port}", 37 | type=master, 38 | connection_timeout=500, 39 | connection_ttl=0, 40 | command_timeout=0, 41 | max_command_retries=0, 42 | shared_connections=true, 43 | max_connections=32, 44 | password="", 45 | sickness_ttl=0, 46 | ignore_slaves=false, 47 | max_cluster_hops=0); 48 | } 49 | } -start 50 | 51 | varnish v1 -vcl+backend { 52 | } 53 | 54 | varnish v1 -cliok "vcl.state vcl1 cold" 55 | 56 | varnish v1 -cliok "vcl.state vcl1 warm" 57 | 58 | varnish v1 -cliok "vcl.use vcl1" 59 | 60 | varnish v1 -cliok "vcl.use vcl2" 61 | 62 | varnish v1 -cliok "vcl.discard vcl1" 63 | 64 | varnish v1 -expect MGT.child_panic == 0 65 | -------------------------------------------------------------------------------- /src/tests/standalone.auto-role.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test 'auto' role" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=auto, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=2, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | db.add_server("${redis_slave1_1_ip}:${redis_slave1_1_port}", auto); 35 | } 36 | 37 | sub vcl_deliver { 38 | # Fetch role (master). 39 | db.command("ROLE"); 40 | db.execute(true); 41 | if (db.reply_is_array() && 42 | db.get_array_reply_length() > 0 && 43 | db.array_reply_is_string(0)) { 44 | set resp.http.Reply-1 = db.get_array_reply_value(0); 45 | } 46 | 47 | # Fetch role (slave). 48 | db.command("ROLE"); 49 | db.execute(false); 50 | if (db.reply_is_array() && 51 | db.get_array_reply_length() > 0 && 52 | db.array_reply_is_string(0)) { 53 | set resp.http.Reply-2 = db.get_array_reply_value(0); 54 | } 55 | 56 | # Stats. 57 | set resp.http.db-stats = db.stats(); 58 | set resp.http.db-servers-total = db.counter("servers.total"); 59 | set resp.http.db-connections-total = db.counter("connections.total"); 60 | set resp.http.db-commands-total = db.counter("commands.total"); 61 | } 62 | } -start 63 | 64 | client c1 { 65 | txreq 66 | rxresp 67 | 68 | expect resp.http.Reply-1 == "master" 69 | 70 | expect resp.http.Reply-2 == "slave" 71 | 72 | expect resp.http.db-servers-total == "2" 73 | expect resp.http.db-connections-total == "2" 74 | expect resp.http.db-commands-total == "2" 75 | } -run 76 | 77 | varnish v1 -expect client_req == 1 78 | -------------------------------------------------------------------------------- /src/tests/standalone.basics-using-private-pool.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests basics using private pool" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | } 35 | 36 | sub vcl_deliver { 37 | # SET. 38 | db.command("SET"); 39 | db.push("foo"); 40 | db.push("hello"); 41 | db.execute(true); 42 | if (db.reply_is_status()) { 43 | set resp.http.Reply-1 = db.get_status_reply(); 44 | } 45 | 46 | # SETEX. 47 | db.command("SETEX"); 48 | db.push("bar"); 49 | db.push("3600"); 50 | db.push("Hello world!"); 51 | db.execute(true); 52 | if (db.reply_is_status()) { 53 | set resp.http.Reply-2 = db.get_status_reply(); 54 | } 55 | 56 | # GET. 57 | db.command("GET"); 58 | db.push("bar"); 59 | db.execute(true); 60 | if (db.reply_is_string()) { 61 | set resp.http.Reply-3 = db.get_string_reply(); 62 | } 63 | 64 | # DEL. 65 | db.command("DEL"); 66 | db.push("bar"); 67 | db.execute(true); 68 | if (db.reply_is_integer()) { 69 | set resp.http.Reply-4 = db.get_integer_reply(); 70 | } 71 | 72 | # MGET. 73 | db.command("MGET"); 74 | db.push("foo"); 75 | db.push("bar"); 76 | db.execute(true); 77 | if (db.reply_is_array()) { 78 | set resp.http.Reply-5-Length = db.get_array_reply_length(); 79 | set resp.http.Reply-5-Value-1 = db.get_array_reply_value(0); 80 | set resp.http.Reply-5-Value-2 = db.get_array_reply_value(1); 81 | } 82 | 83 | # HMSET. 84 | db.command("HMSET"); 85 | db.push("bar"); 86 | db.push("field1"); 87 | db.push("Hello world!"); 88 | db.push("field2"); 89 | db.push("42"); 90 | db.execute(true); 91 | if (db.reply_is_status()) { 92 | set resp.http.Reply-6 = db.get_status_reply(); 93 | } 94 | 95 | # HMGET. 96 | db.command("HGET"); 97 | db.push("bar"); 98 | db.push("field1"); 99 | db.execute(true); 100 | if (db.reply_is_string()) { 101 | set resp.http.Reply-7 = db.get_string_reply(); 102 | } 103 | 104 | # INCR. 105 | db.command("INCR"); 106 | db.push("foo"); 107 | db.execute(true); 108 | if (db.reply_is_error()) { 109 | set resp.http.Reply-8 = db.get_error_reply(); 110 | } 111 | 112 | # EVAL. 113 | set req.http.Script = {" 114 | redis.call('SET', KEYS[1], ARGV[1]) 115 | redis.call('SET', KEYS[2], ARGV[1]) 116 | "}; 117 | db.command("EVAL"); 118 | db.push(req.http.Script); 119 | db.push("2"); 120 | db.push("foo"); 121 | db.push("bar"); 122 | db.push("Atomic!"); 123 | db.execute(true); 124 | if (db.reply_is_nil()) { 125 | set resp.http.Reply-9 = "o/"; 126 | } 127 | 128 | # EVAL. 129 | db.command("EVAL"); 130 | db.push(req.http.Script); 131 | db.push("2"); 132 | db.push("foo"); 133 | db.push("bar"); 134 | db.push("Atomic x 2!"); 135 | db.execute(true); 136 | if (db.reply_is_nil()) { 137 | set resp.http.Reply-10 = "o/"; 138 | } 139 | 140 | # GET. 141 | db.command("GET"); 142 | db.push("foo"); 143 | db.execute(true); 144 | if (db.reply_is_string()) { 145 | set resp.http.Reply-11 = db.get_string_reply(); 146 | } 147 | 148 | # EVAL (large integer). 149 | set req.http.Script = {" 150 | return tonumber(ARGV[1]) 151 | "}; 152 | db.command("EVAL"); 153 | db.push(req.http.Script); 154 | db.push("0"); 155 | db.push("34359738368"); # 2^39 -- See VRT_INTEGER_MAX & VRT_DECIMAL_MAX. 156 | db.execute(true); 157 | if (db.reply_is_integer()) { 158 | set resp.http.Reply-12 = db.get_integer_reply(); 159 | } 160 | 161 | # Stats. 162 | set resp.http.db-stats = db.stats(); 163 | set resp.http.db-servers-total = db.counter("servers.total"); 164 | set resp.http.db-connections-total = db.counter("connections.total"); 165 | set resp.http.db-commands-total = db.counter("commands.total"); 166 | set resp.http.db-commands-error = db.counter("commands.error"); 167 | set resp.http.db-commands-noscript = db.counter("commands.noscript"); 168 | } 169 | } -start 170 | 171 | # Needed for testing purposes, to store scripts in HTTP headers. 172 | varnish v1 -cliok "param.set feature -validate_headers" 173 | 174 | client c1 { 175 | txreq 176 | rxresp 177 | 178 | expect resp.http.Reply-1 == "OK" 179 | 180 | expect resp.http.Reply-2 == "OK" 181 | 182 | expect resp.http.Reply-3 == "Hello world!" 183 | 184 | expect resp.http.Reply-4 == "1" 185 | 186 | expect resp.http.Reply-5-Length == "2" 187 | expect resp.http.Reply-5-Value-1 == "hello" 188 | expect resp.http.Reply-5-Value-2 == "" 189 | 190 | expect resp.http.Reply-6 == "OK" 191 | 192 | expect resp.http.Reply-7 == "Hello world!" 193 | 194 | expect resp.http.Reply-8 != "" 195 | 196 | expect resp.http.Reply-9 == "o/" 197 | 198 | expect resp.http.Reply-10 == "o/" 199 | 200 | expect resp.http.Reply-11 == "Atomic x 2!" 201 | 202 | expect resp.http.Reply-12 == "34359738368" 203 | 204 | expect resp.http.db-servers-total == "1" 205 | expect resp.http.db-connections-total == "1" 206 | expect resp.http.db-commands-total == "12" 207 | expect resp.http.db-commands-error == "1" 208 | expect resp.http.db-commands-noscript == "2" 209 | } -run 210 | 211 | varnish v1 -expect client_req == 1 212 | -------------------------------------------------------------------------------- /src/tests/standalone.basics-with-multiple-servers-and-private-pool.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests basics with multiple servers and private pool" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new master1 = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | 35 | new master2 = redis.db( 36 | location="${redis_master2_ip}:${redis_master2_port}", 37 | type=master, 38 | connection_timeout=500, 39 | connection_ttl=0, 40 | command_timeout=0, 41 | max_command_retries=0, 42 | shared_connections=false, 43 | max_connections=1, 44 | password="", 45 | sickness_ttl=0, 46 | ignore_slaves=false, 47 | max_cluster_hops=0); 48 | } 49 | 50 | sub vcl_deliver { 51 | # SET (master1) -> GET (master1) -> GET (master2). 52 | master1.command("SET"); 53 | master1.push("foo"); 54 | master1.push("Hello world!"); 55 | master1.execute(true); 56 | 57 | master1.command("GET"); 58 | master1.push("foo"); 59 | master1.execute(true); 60 | set resp.http.Reply-1 = master1.get_reply(); 61 | 62 | master2.command("GET"); 63 | master2.push("foo"); 64 | master2.execute(true); 65 | set resp.http.Reply-2 = master2.get_reply(); 66 | 67 | # SET (master2) -> GET (master2) -> GET (master1). 68 | master2.command("SET"); 69 | master2.push("bar"); 70 | master2.push("Hello world!"); 71 | master2.execute(true); 72 | 73 | master2.command("GET"); 74 | master2.push("bar"); 75 | master2.execute(true); 76 | set resp.http.Reply-3 = master2.get_reply(); 77 | 78 | master1.command("GET"); 79 | master1.push("bar"); 80 | master1.execute(true); 81 | set resp.http.Reply-4 = master1.get_reply(); 82 | 83 | # Stats. 84 | set resp.http.master1-stats = master1.stats(); 85 | set resp.http.master1-servers-total = master1.counter("servers.total"); 86 | set resp.http.master1-connections-total = master1.counter("connections.total"); 87 | set resp.http.master1-connections-dropped-overflow = master1.counter("connections.dropped.overflow"); 88 | set resp.http.master1-commands-total = master1.counter("commands.total"); 89 | set resp.http.master1-commands-error = master1.counter("commands.error"); 90 | set resp.http.master1-commands-noscript = master1.counter("commands.noscript"); 91 | 92 | set resp.http.master2-stats = master2.stats(); 93 | set resp.http.master2-servers-total = master2.counter("servers.total"); 94 | set resp.http.master2-connections-dropped-overflow = master2.counter("connections.dropped.overflow"); 95 | set resp.http.master2-connections-total = master2.counter("connections.total"); 96 | set resp.http.master2-commands-total = master2.counter("commands.total"); 97 | set resp.http.master2-commands-error = master2.counter("commands.error"); 98 | set resp.http.master2-commands-noscript = master2.counter("commands.noscript"); 99 | } 100 | } -start 101 | 102 | client c1 { 103 | txreq 104 | rxresp 105 | 106 | expect resp.http.Reply-1 == "Hello world!" 107 | expect resp.http.Reply-2 == "" 108 | 109 | expect resp.http.Reply-3 == "Hello world!" 110 | expect resp.http.Reply-4 == "" 111 | 112 | expect resp.http.master1-servers-total == "1" 113 | expect resp.http.master1-connections-total == "2" 114 | expect resp.http.master1-connections-dropped-overflow == "1" 115 | expect resp.http.master1-commands-total == "3" 116 | expect resp.http.master1-commands-error == "0" 117 | expect resp.http.master1-commands-noscript == "0" 118 | 119 | expect resp.http.master2-servers-total == "1" 120 | expect resp.http.master2-connections-total == "1" 121 | expect resp.http.master2-connections-dropped-overflow == "1" 122 | expect resp.http.master2-commands-total == "3" 123 | expect resp.http.master2-commands-error == "0" 124 | expect resp.http.master2-commands-noscript == "0" 125 | } -run 126 | 127 | varnish v1 -expect client_req == 1 128 | -------------------------------------------------------------------------------- /src/tests/standalone.basics-with-multiple-servers-and-shared-pool.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests basics with multiple servers and shared pool" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new master1 = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=true, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | 35 | new master2 = redis.db( 36 | location="${redis_master2_ip}:${redis_master2_port}", 37 | type=master, 38 | connection_timeout=500, 39 | connection_ttl=0, 40 | command_timeout=0, 41 | max_command_retries=0, 42 | shared_connections=true, 43 | max_connections=1, 44 | password="", 45 | sickness_ttl=0, 46 | ignore_slaves=false, 47 | max_cluster_hops=0); 48 | } 49 | 50 | sub vcl_deliver { 51 | # SET (master1) -> GET (master1) -> GET (master2). 52 | master1.command("SET"); 53 | master1.push("foo"); 54 | master1.push("Hello world!"); 55 | master1.execute(true); 56 | 57 | master1.command("GET"); 58 | master1.push("foo"); 59 | master1.execute(true); 60 | set resp.http.Reply-1 = master1.get_reply(); 61 | 62 | master2.command("GET"); 63 | master2.push("foo"); 64 | master2.execute(true); 65 | set resp.http.Reply-2 = master2.get_reply(); 66 | 67 | # SET (master2) -> GET (master2) -> GET (master1). 68 | master2.command("SET"); 69 | master2.push("bar"); 70 | master2.push("Hello world!"); 71 | master2.execute(true); 72 | 73 | master2.command("GET"); 74 | master2.push("bar"); 75 | master2.execute(true); 76 | set resp.http.Reply-3 = master2.get_reply(); 77 | 78 | master1.command("GET"); 79 | master1.push("bar"); 80 | master1.execute(true); 81 | set resp.http.Reply-4 = master1.get_reply(); 82 | 83 | # Stats. 84 | set resp.http.master1-stats = master1.stats(); 85 | set resp.http.master1-servers-total = master1.counter("servers.total"); 86 | set resp.http.master1-connections-total = master1.counter("connections.total"); 87 | set resp.http.master1-connections-dropped-overflow = master1.counter("connections.dropped.overflow"); 88 | set resp.http.master1-commands-total = master1.counter("commands.total"); 89 | set resp.http.master1-commands-error = master1.counter("commands.error"); 90 | set resp.http.master1-commands-noscript = master1.counter("commands.noscript"); 91 | 92 | set resp.http.master2-stats = master2.stats(); 93 | set resp.http.master2-servers-total = master2.counter("servers.total"); 94 | set resp.http.master2-connections-dropped-overflow = master2.counter("connections.dropped.overflow"); 95 | set resp.http.master2-connections-total = master2.counter("connections.total"); 96 | set resp.http.master2-commands-total = master2.counter("commands.total"); 97 | set resp.http.master2-commands-error = master2.counter("commands.error"); 98 | set resp.http.master2-commands-noscript = master2.counter("commands.noscript"); 99 | } 100 | } -start 101 | 102 | client c1 { 103 | txreq 104 | rxresp 105 | 106 | expect resp.http.Reply-1 == "Hello world!" 107 | expect resp.http.Reply-2 == "" 108 | 109 | expect resp.http.Reply-3 == "Hello world!" 110 | expect resp.http.Reply-4 == "" 111 | 112 | expect resp.http.master1-servers-total == "1" 113 | expect resp.http.master1-connections-total == "1" 114 | expect resp.http.master1-connections-dropped-overflow == "0" 115 | expect resp.http.master1-commands-total == "3" 116 | expect resp.http.master1-commands-error == "0" 117 | expect resp.http.master1-commands-noscript == "0" 118 | 119 | expect resp.http.master2-servers-total == "1" 120 | expect resp.http.master2-connections-total == "1" 121 | expect resp.http.master2-connections-dropped-overflow == "0" 122 | expect resp.http.master2-commands-total == "3" 123 | expect resp.http.master2-commands-error == "0" 124 | expect resp.http.master2-commands-noscript == "0" 125 | } -run 126 | 127 | varnish v1 -expect client_req == 1 128 | -------------------------------------------------------------------------------- /src/tests/standalone.blocked-workers.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests workers are blocked when using a small shared pool" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 2 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=true, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | } 35 | 36 | sub vcl_deliver { 37 | # Slow vs fast command. 38 | if (req.http.Slow == "1") { 39 | db.command("DEBUG"); 40 | db.push("sleep"); 41 | db.push("3"); 42 | } else { 43 | db.command("SET"); 44 | db.push("foo"); 45 | db.push("hello"); 46 | } 47 | db.execute(true); 48 | set resp.http.Replied = db.replied(); 49 | set resp.http.Reply = db.get_reply(); 50 | 51 | # Stats. 52 | set resp.http.db-stats = db.stats(); 53 | set resp.http.db-servers-total = db.counter("servers.total"); 54 | set resp.http.db-connections-total = db.counter("connections.total"); 55 | set resp.http.db-workers-blocked = db.counter("workers.blocked"); 56 | set resp.http.db-commands-total = db.counter("commands.total"); 57 | } 58 | } -start 59 | 60 | client c1 { 61 | txreq -hdr "Slow: 1" 62 | rxresp 63 | 64 | expect resp.http.Replied == "true" 65 | expect resp.http.Reply == "OK" 66 | } -start 67 | 68 | delay 1.0 69 | 70 | client c2 { 71 | txreq -hdr "Slow: 0" 72 | rxresp 73 | 74 | expect resp.http.Replied == "true" 75 | expect resp.http.Reply == "OK" 76 | 77 | expect resp.http.db-servers-total == "1" 78 | expect resp.http.db-connections-total == "1" 79 | expect resp.http.db-workers-blocked == "1" 80 | expect resp.http.db-commands-total == "2" 81 | } -run 82 | 83 | varnish v1 -expect client_req == 2 84 | -------------------------------------------------------------------------------- /src/tests/standalone.command-execution-timeout.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests command execution timeout" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | } 35 | 36 | sub vcl_deliver { 37 | # Fast command (no timeout). 38 | db.command("SET"); 39 | db.push("foo"); 40 | db.push("hello"); 41 | db.execute(true); 42 | set resp.http.Replied-1 = db.replied(); 43 | set resp.http.Reply-1 = db.get_reply(); 44 | 45 | # Slow command (no timeout). 46 | db.command("DEBUG"); 47 | db.push("sleep"); 48 | db.push("3"); 49 | db.execute(true); 50 | set resp.http.Replied-2 = db.replied(); 51 | set resp.http.Reply-2 = db.get_reply(); 52 | 53 | # Fast command (1000 ms timeout). 54 | db.command("SET"); 55 | db.push("foo"); 56 | db.push("hello"); 57 | db.timeout(1000); 58 | db.execute(true); 59 | set resp.http.Replied-3 = db.replied(); 60 | set resp.http.Reply-3 = db.get_reply(); 61 | 62 | # Slow command (1000 ms timeout). 63 | db.command("DEBUG"); 64 | db.push("sleep"); 65 | db.push("3"); 66 | db.timeout(1000); 67 | db.execute(true); 68 | set resp.http.Replied-4 = db.replied(); 69 | set resp.http.Reply-4 = db.get_reply(); 70 | 71 | # Fast command (no timeout). 72 | db.command("SET"); 73 | db.push("foo"); 74 | db.push("hello"); 75 | db.execute(true); 76 | set resp.http.Replied-5 = db.replied(); 77 | set resp.http.Reply-5 = db.get_reply(); 78 | 79 | # Slow command (no timeout). 80 | db.command("DEBUG"); 81 | db.push("sleep"); 82 | db.push("3"); 83 | db.execute(true); 84 | set resp.http.Replied-6 = db.replied(); 85 | set resp.http.Reply-6 = db.get_reply(); 86 | 87 | # Stats. 88 | set resp.http.db-stats = db.stats(); 89 | set resp.http.db-servers-total = db.counter("servers.total"); 90 | set resp.http.db-connections-total = db.counter("connections.total"); 91 | set resp.http.db-connections-dropped-error = db.counter("connections.dropped.error"); 92 | set resp.http.db-commands-total = db.counter("commands.total"); 93 | set resp.http.db-commands-error = db.counter("commands.error"); 94 | set resp.http.db-commands-failed = db.counter("commands.failed"); 95 | set resp.http.db-commands-noscript = db.counter("commands.noscript"); 96 | } 97 | } -start 98 | 99 | client c1 { 100 | txreq 101 | rxresp 102 | 103 | expect resp.http.Replied-1 == "true" 104 | expect resp.http.Reply-1 == "OK" 105 | 106 | expect resp.http.Replied-2 == "true" 107 | expect resp.http.Reply-2 == "OK" 108 | 109 | expect resp.http.Replied-3 == "true" 110 | expect resp.http.Reply-3 == "OK" 111 | 112 | expect resp.http.Replied-4 == "false" 113 | expect resp.http.Reply-4 == "" 114 | 115 | expect resp.http.Replied-5 == "true" 116 | expect resp.http.Reply-5 == "OK" 117 | 118 | expect resp.http.Replied-6 == "true" 119 | expect resp.http.Reply-6 == "OK" 120 | 121 | expect resp.http.db-servers-total == "1" 122 | expect resp.http.db-connections-total == "2" 123 | expect resp.http.db-connections-dropped-error == "1" 124 | expect resp.http.db-commands-total == "5" 125 | expect resp.http.db-commands-failed == "1" 126 | expect resp.http.db-commands-error == "0" 127 | expect resp.http.db-commands-noscript == "0" 128 | } -run 129 | 130 | varnish v1 -expect client_req == 1 131 | -------------------------------------------------------------------------------- /src/tests/standalone.easy-command-timeout.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests easy_execution timeout" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | } 35 | 36 | sub vcl_deliver { 37 | # Fast command (no timeout). 38 | db.easy_execute("SET", "foo", "hello", master = true); 39 | set resp.http.Replied-1 = db.replied(); 40 | set resp.http.Reply-1 = db.get_reply(); 41 | 42 | # Slow command (no timeout). 43 | db.easy_execute("DEBUG", "sleep", "3", master = true); 44 | set resp.http.Replied-2 = db.replied(); 45 | set resp.http.Reply-2 = db.get_reply(); 46 | 47 | # Fast command (1000 ms timeout). 48 | db.easy_execute("SET", "foo", "hello", master = true, timeout = 1000); 49 | set resp.http.Replied-3 = db.replied(); 50 | set resp.http.Reply-3 = db.get_reply(); 51 | 52 | # Slow command (1000 ms timeout). 53 | db.easy_execute("DEBUG", "sleep", "3", master = true, timeout = 1000); 54 | set resp.http.Replied-4 = db.replied(); 55 | set resp.http.Reply-4 = db.get_reply(); 56 | 57 | # Fast command (no timeout). 58 | db.easy_execute("SET", "foo", "hello", master = true); 59 | set resp.http.Replied-5 = db.replied(); 60 | set resp.http.Reply-5 = db.get_reply(); 61 | 62 | # Slow command (no timeout). 63 | db.easy_execute("DEBUG", "sleep", "3", master = true); 64 | set resp.http.Replied-6 = db.replied(); 65 | set resp.http.Reply-6 = db.get_reply(); 66 | 67 | # Stats. 68 | set resp.http.db-stats = db.stats(); 69 | set resp.http.db-servers-total = db.counter("servers.total"); 70 | set resp.http.db-connections-total = db.counter("connections.total"); 71 | set resp.http.db-connections-dropped-error = db.counter("connections.dropped.error"); 72 | set resp.http.db-commands-total = db.counter("commands.total"); 73 | set resp.http.db-commands-error = db.counter("commands.error"); 74 | set resp.http.db-commands-failed = db.counter("commands.failed"); 75 | set resp.http.db-commands-noscript = db.counter("commands.noscript"); 76 | } 77 | } -start 78 | 79 | client c1 { 80 | txreq 81 | rxresp 82 | 83 | expect resp.http.Replied-1 == "true" 84 | expect resp.http.Reply-1 == "OK" 85 | 86 | expect resp.http.Replied-2 == "true" 87 | expect resp.http.Reply-2 == "OK" 88 | 89 | expect resp.http.Replied-3 == "true" 90 | expect resp.http.Reply-3 == "OK" 91 | 92 | expect resp.http.Replied-4 == "false" 93 | expect resp.http.Reply-4 == "" 94 | 95 | expect resp.http.Replied-5 == "true" 96 | expect resp.http.Reply-5 == "OK" 97 | 98 | expect resp.http.Replied-6 == "true" 99 | expect resp.http.Reply-6 == "OK" 100 | 101 | expect resp.http.db-servers-total == "1" 102 | expect resp.http.db-connections-total == "2" 103 | expect resp.http.db-connections-dropped-error == "1" 104 | expect resp.http.db-commands-total == "5" 105 | expect resp.http.db-commands-failed == "1" 106 | expect resp.http.db-commands-error == "0" 107 | expect resp.http.db-commands-noscript == "0" 108 | } -run 109 | 110 | varnish v1 -expect client_req == 1 111 | -------------------------------------------------------------------------------- /src/tests/standalone.password-protected-instances.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests password protected instances" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 3 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | } 35 | 36 | sub vcl_deliver { 37 | # SET. 38 | db.command("SET"); 39 | db.push("foo"); 40 | db.push("hello"); 41 | db.execute(true); 42 | if (db.reply_is_status()) { 43 | set resp.http.Reply-1 = db.get_status_reply(); 44 | } 45 | 46 | # CONFIG SET. 47 | db.command("CONFIG"); 48 | db.push("SET"); 49 | db.push("requirepass"); 50 | db.push("s3cr3t"); 51 | db.execute(true); 52 | if (db.reply_is_status()) { 53 | set resp.http.Reply-2 = db.get_status_reply(); 54 | } 55 | } 56 | } -start 57 | 58 | varnish v2 -arg "-p vsl_reclen=1024" -vcl+backend { 59 | import ${vmod_redis}; 60 | 61 | sub vcl_init { 62 | redis.subnets( 63 | masks={""}); 64 | 65 | redis.sentinels( 66 | locations={""}, 67 | period=0, 68 | connection_timeout=500, 69 | command_timeout=0); 70 | 71 | new db1 = redis.db( 72 | location="${redis_master1_ip}:${redis_master1_port}", 73 | type=master, 74 | connection_timeout=500, 75 | connection_ttl=0, 76 | command_timeout=0, 77 | max_command_retries=0, 78 | shared_connections=false, 79 | max_connections=3, 80 | password="", 81 | sickness_ttl=0, 82 | ignore_slaves=false, 83 | max_cluster_hops=0); 84 | 85 | new db2 = redis.db( 86 | location="${redis_master1_ip}:${redis_master1_port}", 87 | type=master, 88 | connection_timeout=500, 89 | connection_ttl=0, 90 | command_timeout=0, 91 | max_command_retries=0, 92 | shared_connections=false, 93 | max_connections=3, 94 | password="42", 95 | sickness_ttl=0, 96 | ignore_slaves=false, 97 | max_cluster_hops=0); 98 | 99 | new db3 = redis.db( 100 | location="${redis_master1_ip}:${redis_master1_port}", 101 | type=master, 102 | connection_timeout=500, 103 | connection_ttl=0, 104 | command_timeout=0, 105 | max_command_retries=0, 106 | shared_connections=false, 107 | max_connections=3, 108 | password="s3cr3t", 109 | sickness_ttl=0, 110 | ignore_slaves=false, 111 | max_cluster_hops=0); 112 | } 113 | 114 | sub vcl_deliver { 115 | # GET (no password). 116 | db1.command("GET"); 117 | db1.push("foo"); 118 | db1.execute(true); 119 | if (db1.reply_is_error() && 120 | (db1.get_error_reply() ~ "NOAUTH")) { 121 | set resp.http.Reply-1 = "o/"; 122 | } 123 | 124 | # GET (wrong password). 125 | db2.command("GET"); 126 | db2.push("foo"); 127 | db2.execute(true); 128 | if (!db2.replied()) { 129 | set resp.http.Reply-2 = "o/"; 130 | } 131 | 132 | # GET (right password). 133 | db3.command("GET"); 134 | db3.push("foo"); 135 | db3.execute(true); 136 | if (db3.reply_is_string()) { 137 | set resp.http.Reply-3 = db3.get_string_reply(); 138 | } 139 | 140 | # ACL. 141 | if (${redis_version} >= 6000000) { 142 | db3.command("ACL"); 143 | db3.push("SETUSER"); 144 | db3.push("alice"); 145 | db3.push("on"); 146 | db3.push(">t0ps3cr3t"); 147 | db3.push("~foo*"); 148 | db3.push("+get"); 149 | db3.execute(true); 150 | if (db3.reply_is_status()) { 151 | set resp.http.Reply-4 = db3.get_status_reply(); 152 | } 153 | } else { 154 | set resp.http.Reply-4 = "OK"; 155 | } 156 | } 157 | } -start 158 | 159 | varnish v3 -arg "-p vsl_reclen=1024" -vcl+backend { 160 | import ${vmod_redis}; 161 | 162 | sub vcl_init { 163 | redis.subnets( 164 | masks={""}); 165 | 166 | redis.sentinels( 167 | locations={""}, 168 | period=0, 169 | connection_timeout=500, 170 | command_timeout=0); 171 | 172 | new db1 = redis.db( 173 | location="${redis_master1_ip}:${redis_master1_port}", 174 | type=master, 175 | connection_timeout=500, 176 | connection_ttl=0, 177 | command_timeout=0, 178 | max_command_retries=0, 179 | shared_connections=false, 180 | max_connections=3, 181 | user="alice", 182 | password="", 183 | sickness_ttl=0, 184 | ignore_slaves=false, 185 | max_cluster_hops=0); 186 | 187 | new db2 = redis.db( 188 | location="${redis_master1_ip}:${redis_master1_port}", 189 | type=master, 190 | connection_timeout=500, 191 | connection_ttl=0, 192 | command_timeout=0, 193 | max_command_retries=0, 194 | shared_connections=false, 195 | max_connections=3, 196 | user="alice", 197 | password="s3cr3t", 198 | sickness_ttl=0, 199 | ignore_slaves=false, 200 | max_cluster_hops=0); 201 | 202 | new db3 = redis.db( 203 | location="${redis_master1_ip}:${redis_master1_port}", 204 | type=master, 205 | connection_timeout=500, 206 | connection_ttl=0, 207 | command_timeout=0, 208 | max_command_retries=0, 209 | shared_connections=false, 210 | max_connections=3, 211 | user="alice", 212 | password="t0ps3cr3t", 213 | sickness_ttl=0, 214 | ignore_slaves=false, 215 | max_cluster_hops=0); 216 | } 217 | 218 | sub vcl_deliver { 219 | # GET (no password). 220 | if (${redis_version} >= 6000000) { 221 | db1.command("GET"); 222 | db1.push("foo"); 223 | db1.execute(true); 224 | if (db1.reply_is_error() && (db1.get_error_reply() ~ "NOAUTH")) { 225 | set resp.http.Reply-1 = "o/"; 226 | } 227 | } else { 228 | set resp.http.Reply-1 = "o/"; 229 | } 230 | 231 | # GET (wrong password). 232 | if (${redis_version} >= 6000000) { 233 | db2.command("GET"); 234 | db2.push("foo"); 235 | db2.execute(true); 236 | if (!db2.replied()) { 237 | set resp.http.Reply-2 = "o/"; 238 | } 239 | } else { 240 | set resp.http.Reply-2 = "o/"; 241 | } 242 | 243 | # GET (right password). 244 | if (${redis_version} >= 6000000) { 245 | db3.command("GET"); 246 | db3.push("foo"); 247 | db3.execute(true); 248 | if (db3.reply_is_string()) { 249 | set resp.http.Reply-3 = db3.get_string_reply(); 250 | } 251 | } else { 252 | set resp.http.Reply-3 = "hello"; 253 | } 254 | 255 | # GET (right password but no permissions). 256 | if (${redis_version} >= 6000000) { 257 | db3.command("GET"); 258 | db3.push("bar"); 259 | db3.execute(true); 260 | if (db3.reply_is_error() && 261 | (db3.get_error_reply() ~ "NOPERM")) { 262 | set resp.http.Reply-4 = "o/"; 263 | } 264 | } else { 265 | set resp.http.Reply-4 = "o/"; 266 | } 267 | } 268 | } -start 269 | 270 | client c1 { 271 | txreq 272 | rxresp 273 | 274 | expect resp.http.Reply-1 == "OK" 275 | 276 | expect resp.http.Reply-2 == "OK" 277 | } -run 278 | 279 | client c2 -connect ${v2_sock} { 280 | txreq 281 | rxresp 282 | 283 | expect resp.http.Reply-1 == "o/" 284 | 285 | expect resp.http.Reply-2 == "o/" 286 | 287 | expect resp.http.Reply-3 == "hello" 288 | 289 | expect resp.http.Reply-4 == "OK" 290 | } -run 291 | 292 | client c3 -connect ${v3_sock} { 293 | txreq 294 | rxresp 295 | 296 | expect resp.http.Reply-1 == "o/" 297 | 298 | expect resp.http.Reply-2 == "o/" 299 | 300 | expect resp.http.Reply-3 == "hello" 301 | 302 | expect resp.http.Reply-4 == "o/" 303 | } -run 304 | 305 | varnish v1 -expect client_req == 1 306 | varnish v2 -expect client_req == 1 307 | varnish v3 -expect client_req == 1 308 | -------------------------------------------------------------------------------- /src/tests/standalone.prometheus-stats.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test Prometheus stats" 2 | 3 | server s1 { 4 | } -repeat 1 -start 5 | 6 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 7 | import ${vmod_redis}; 8 | 9 | sub vcl_init { 10 | redis.subnets( 11 | masks={""}); 12 | 13 | redis.sentinels( 14 | locations={""}, 15 | period=0, 16 | connection_timeout=500, 17 | command_timeout=0); 18 | 19 | new db = redis.db( 20 | location="${redis_master1_ip}:${redis_master1_port}", 21 | type=master, 22 | connection_timeout=500, 23 | connection_ttl=0, 24 | command_timeout=0, 25 | max_command_retries=0, 26 | shared_connections=false, 27 | max_connections=1, 28 | password="", 29 | sickness_ttl=0, 30 | ignore_slaves=false, 31 | max_cluster_hops=0); 32 | } 33 | 34 | sub vcl_recv { 35 | return (synth(200, "OK")); 36 | } 37 | 38 | sub vcl_synth { 39 | if (req.http.stream) { 40 | db.stats( 41 | format=prometheus, 42 | stream=true, 43 | prometheus_name_prefix=req.http.prefix, 44 | prometheus_default_labels=(req.http.default-labels == "1"), 45 | prometheus_extra_labels=req.http.extra-labels); 46 | } else { 47 | synthetic(db.stats( 48 | format=prometheus, 49 | stream=false, 50 | prometheus_name_prefix=req.http.prefix, 51 | prometheus_default_labels=(req.http.default-labels == "1"), 52 | prometheus_extra_labels=req.http.extra-labels)); 53 | } 54 | return (deliver); 55 | } 56 | } -start 57 | 58 | client c1 { 59 | txreq 60 | rxresp 61 | expect resp.body == {# TYPE vmod_redis_servers_total counter 62 | vmod_redis_servers_total{} 1 63 | # TYPE vmod_redis_servers_failed counter 64 | vmod_redis_servers_failed{} 0 65 | # TYPE vmod_redis_connections_total counter 66 | vmod_redis_connections_total{} 0 67 | # TYPE vmod_redis_connections_failed counter 68 | vmod_redis_connections_failed{} 0 69 | # TYPE vmod_redis_connections_dropped counter 70 | vmod_redis_connections_dropped{reason="error"} 0 71 | vmod_redis_connections_dropped{reason="hung_up"} 0 72 | vmod_redis_connections_dropped{reason="overflow"} 0 73 | vmod_redis_connections_dropped{reason="ttl"} 0 74 | vmod_redis_connections_dropped{reason="version"} 0 75 | vmod_redis_connections_dropped{reason="sick"} 0 76 | # TYPE vmod_redis_workers_blocked counter 77 | vmod_redis_workers_blocked{} 0 78 | # TYPE vmod_redis_commands_total counter 79 | vmod_redis_commands_total{} 0 80 | # TYPE vmod_redis_commands_failed counter 81 | vmod_redis_commands_failed{} 0 82 | # TYPE vmod_redis_commands_retried counter 83 | vmod_redis_commands_retried{} 0 84 | # TYPE vmod_redis_commands_error counter 85 | vmod_redis_commands_error{} 0 86 | # TYPE vmod_redis_commands_noscript counter 87 | vmod_redis_commands_noscript{} 0 88 | # TYPE vmod_redis_cluster_discoveries_total counter 89 | vmod_redis_cluster_discoveries_total{} 0 90 | # TYPE vmod_redis_cluster_discoveries_failed counter 91 | vmod_redis_cluster_discoveries_failed{} 0 92 | # TYPE vmod_redis_cluster_replies_moved counter 93 | vmod_redis_cluster_replies_moved{} 0 94 | # TYPE vmod_redis_cluster_replies_ask counter 95 | vmod_redis_cluster_replies_ask{} 0 96 | } 97 | 98 | txreq -hdr "stream: 1" \ 99 | -hdr "prefix: foo_" \ 100 | -hdr "default-labels: 1" \ 101 | -hdr {extra-labels: this="bar",that="baz"} 102 | rxresp 103 | expect resp.body == {# TYPE foo_servers_total counter 104 | foo_servers_total{this="bar",that="baz",name="db"} 1 105 | # TYPE foo_servers_failed counter 106 | foo_servers_failed{this="bar",that="baz",name="db"} 0 107 | # TYPE foo_connections_total counter 108 | foo_connections_total{this="bar",that="baz",name="db"} 0 109 | # TYPE foo_connections_failed counter 110 | foo_connections_failed{this="bar",that="baz",name="db"} 0 111 | # TYPE foo_connections_dropped counter 112 | foo_connections_dropped{this="bar",that="baz",name="db",reason="error"} 0 113 | foo_connections_dropped{this="bar",that="baz",name="db",reason="hung_up"} 0 114 | foo_connections_dropped{this="bar",that="baz",name="db",reason="overflow"} 0 115 | foo_connections_dropped{this="bar",that="baz",name="db",reason="ttl"} 0 116 | foo_connections_dropped{this="bar",that="baz",name="db",reason="version"} 0 117 | foo_connections_dropped{this="bar",that="baz",name="db",reason="sick"} 0 118 | # TYPE foo_workers_blocked counter 119 | foo_workers_blocked{this="bar",that="baz",name="db"} 0 120 | # TYPE foo_commands_total counter 121 | foo_commands_total{this="bar",that="baz",name="db"} 0 122 | # TYPE foo_commands_failed counter 123 | foo_commands_failed{this="bar",that="baz",name="db"} 0 124 | # TYPE foo_commands_retried counter 125 | foo_commands_retried{this="bar",that="baz",name="db"} 0 126 | # TYPE foo_commands_error counter 127 | foo_commands_error{this="bar",that="baz",name="db"} 0 128 | # TYPE foo_commands_noscript counter 129 | foo_commands_noscript{this="bar",that="baz",name="db"} 0 130 | # TYPE foo_cluster_discoveries_total counter 131 | foo_cluster_discoveries_total{this="bar",that="baz",name="db"} 0 132 | # TYPE foo_cluster_discoveries_failed counter 133 | foo_cluster_discoveries_failed{this="bar",that="baz",name="db"} 0 134 | # TYPE foo_cluster_replies_moved counter 135 | foo_cluster_replies_moved{this="bar",that="baz",name="db"} 0 136 | # TYPE foo_cluster_replies_ask counter 137 | foo_cluster_replies_ask{this="bar",that="baz",name="db"} 0 138 | } 139 | } -run 140 | 141 | varnish v1 -expect client_req == 2 142 | -------------------------------------------------------------------------------- /src/tests/standalone.proxied-methods.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests proxied methods" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new master1 = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | 35 | new master2 = redis.db( 36 | location="${redis_master2_ip}:${redis_master2_port}", 37 | type=master, 38 | connection_timeout=500, 39 | connection_ttl=0, 40 | command_timeout=0, 41 | max_command_retries=0, 42 | shared_connections=false, 43 | max_connections=1, 44 | password="", 45 | sickness_ttl=0, 46 | ignore_slaves=false, 47 | max_cluster_hops=0); 48 | } 49 | 50 | sub vcl_deliver { 51 | # SET (master1) -> GET (master1) -> GET (master2). 52 | redis.use("master1"); 53 | redis.command("SET"); 54 | redis.push("foo"); 55 | redis.push("Hello world!"); 56 | redis.execute(true); 57 | 58 | redis.command("GET"); 59 | redis.push("foo"); 60 | redis.execute(true); 61 | set resp.http.Reply-1 = redis.get_reply(); 62 | 63 | redis.command("GET", db="master2"); 64 | redis.push("foo", db="master2"); 65 | redis.execute(true, db="master2"); 66 | set resp.http.Reply-2 = redis.get_reply(db="master2"); 67 | 68 | # SET (master2) -> GET (master2) -> GET (master1). 69 | redis.use("master2"); 70 | redis.command("SET"); 71 | redis.push("bar"); 72 | redis.push("Hello world!"); 73 | redis.execute(true); 74 | 75 | redis.command("GET"); 76 | redis.push("bar"); 77 | redis.execute(true); 78 | set resp.http.Reply-3 = redis.get_reply(); 79 | 80 | redis.command("GET", db="master1"); 81 | redis.push("bar", db="master1"); 82 | redis.execute(true, db="master1"); 83 | set resp.http.Reply-4 = redis.get_reply(db="master1"); 84 | 85 | # same, with easy_execute 86 | # SET (master1) -> GET (master1) -> GET (master2). 87 | redis.use("master1"); 88 | redis.easy_execute("SET", "baz", "42"); 89 | 90 | redis.easy_execute("GET", "baz"); 91 | set resp.http.Reply-easy-1 = redis.get_reply(); 92 | 93 | redis.easy_execute("GET", "baz", db="master2", master = true); 94 | set resp.http.Reply-easy-2 = redis.get_reply(db="master2"); 95 | 96 | # SET (master2) -> GET (master2) -> GET (master1). 97 | redis.use("master2"); 98 | redis.easy_execute("SET", "qux", "42"); 99 | 100 | redis.command("GET"); 101 | redis.easy_execute("GET", "qux"); 102 | set resp.http.Reply-easy-3 = redis.get_reply(); 103 | 104 | redis.easy_execute("GET", "qux", db="master1"); 105 | set resp.http.Reply-easy-4 = redis.get_reply(db="master1"); 106 | 107 | 108 | # Stats. 109 | set resp.http.master1-stats = redis.stats(db="master1"); 110 | set resp.http.master1-servers-total = redis.counter("servers.total", db="master1"); 111 | redis.use("master1"); 112 | set resp.http.master1-connections-total = redis.counter("connections.total"); 113 | set resp.http.master1-connections-dropped-overflow = redis.counter("connections.dropped.overflow"); 114 | set resp.http.master1-commands-total = redis.counter("commands.total"); 115 | set resp.http.master1-commands-error = redis.counter("commands.error"); 116 | set resp.http.master1-commands-noscript = redis.counter("commands.noscript"); 117 | 118 | set resp.http.master2-stats = redis.stats(db="master2"); 119 | set resp.http.master2-servers-total = redis.counter("servers.total", db="master2"); 120 | redis.use("master2"); 121 | set resp.http.master2-connections-dropped-overflow = redis.counter("connections.dropped.overflow"); 122 | set resp.http.master2-connections-total = redis.counter("connections.total"); 123 | set resp.http.master2-commands-total = redis.counter("commands.total"); 124 | set resp.http.master2-commands-error = redis.counter("commands.error"); 125 | set resp.http.master2-commands-noscript = redis.counter("commands.noscript"); 126 | } 127 | } -start 128 | 129 | client c1 { 130 | txreq 131 | rxresp 132 | 133 | expect resp.http.Reply-1 == "Hello world!" 134 | expect resp.http.Reply-2 == "" 135 | 136 | expect resp.http.Reply-3 == "Hello world!" 137 | expect resp.http.Reply-4 == "" 138 | 139 | expect resp.http.Reply-easy-1 == "42" 140 | expect resp.http.Reply-easy-2 == "" 141 | 142 | expect resp.http.Reply-easy-3 == "42" 143 | expect resp.http.Reply-easy-4 == "" 144 | 145 | expect resp.http.master1-servers-total == "1" 146 | expect resp.http.master1-connections-total == "3" 147 | expect resp.http.master1-connections-dropped-overflow == "2" 148 | expect resp.http.master1-commands-total == "6" 149 | expect resp.http.master1-commands-error == "0" 150 | expect resp.http.master1-commands-noscript == "0" 151 | 152 | expect resp.http.master2-servers-total == "1" 153 | expect resp.http.master2-connections-total == "2" 154 | expect resp.http.master2-connections-dropped-overflow == "2" 155 | expect resp.http.master2-commands-total == "6" 156 | expect resp.http.master2-commands-error == "0" 157 | expect resp.http.master2-commands-noscript == "0" 158 | } -run 159 | 160 | varnish v1 -expect client_req == 1 161 | -------------------------------------------------------------------------------- /src/tests/standalone.subnets-using-private-pool.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests subnets using private pool" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={" 14 | 1 ${redis_slave1_1_ip}/32, 15 | 0 ${redis_slave1_2_ip}/32 16 | "}); 17 | 18 | redis.sentinels( 19 | locations={""}, 20 | period=0, 21 | connection_timeout=500, 22 | command_timeout=0); 23 | 24 | new db = redis.db( 25 | location="${redis_master1_ip}:${redis_master1_port}", 26 | type=master, 27 | connection_timeout=500, 28 | connection_ttl=0, 29 | command_timeout=0, 30 | max_command_retries=0, 31 | shared_connections=false, 32 | max_connections=2, 33 | password="", 34 | sickness_ttl=0, 35 | ignore_slaves=false, 36 | max_cluster_hops=0); 37 | db.add_server("${redis_slave1_1_ip}:${redis_slave1_1_port}", slave); 38 | db.add_server("${redis_slave1_2_ip}:${redis_slave1_2_port}", slave); 39 | } 40 | 41 | sub vcl_deliver { 42 | # INFO (master). 43 | db.command("INFO"); 44 | db.execute(true); 45 | if (db.reply_is_string()) { 46 | set resp.http.Reply-1 = regsub( 47 | db.get_string_reply(), 48 | "(?s)^.*\nrole:([^\s]+)\s.*$", "\1"); 49 | set resp.http.Reply-2 = regsub( 50 | db.get_string_reply(), 51 | "(?s)^.*\ntcp_port:([^\s]+)\s.*$", "\1"); 52 | } 53 | 54 | # INFO (slave). 55 | db.command("INFO"); 56 | db.execute(false); 57 | if (db.reply_is_string()) { 58 | set resp.http.Reply-3 = regsub( 59 | db.get_string_reply(), 60 | "(?s)^.*\nrole:([^\s]+)\s.*$", "\1"); 61 | set resp.http.Reply-4 = regsub( 62 | db.get_string_reply(), 63 | "(?s)^.*\ntcp_port:([^\s]+)\s.*$", "\1"); 64 | } 65 | 66 | # Stats. 67 | set resp.http.db-stats = db.stats(); 68 | set resp.http.db-servers-total = db.counter("servers.total"); 69 | set resp.http.db-connections-total = db.counter("connections.total"); 70 | set resp.http.db-commands-total = db.counter("commands.total"); 71 | set resp.http.db-commands-error = db.counter("commands.error"); 72 | } 73 | } -start 74 | 75 | client c1 { 76 | txreq 77 | rxresp 78 | 79 | expect resp.http.Reply-1 == "master" 80 | expect resp.http.Reply-2 == "${redis_master1_port}" 81 | 82 | expect resp.http.Reply-3 == "slave" 83 | expect resp.http.Reply-4 == "${redis_slave1_2_port}" 84 | 85 | expect resp.http.db-servers-total == "3" 86 | expect resp.http.db-connections-total == "2" 87 | expect resp.http.db-commands-total == "2" 88 | expect resp.http.db-commands-error == "0" 89 | } -run 90 | 91 | varnish v1 -expect client_req == 1 92 | -------------------------------------------------------------------------------- /src/tests/standalone.subnets-using-shared-pool.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Tests subnets using shared pool" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={" 14 | 1 ${redis_slave1_1_ip}/32, 15 | 0 ${redis_slave1_2_ip}/32 16 | "}); 17 | 18 | redis.sentinels( 19 | locations={""}, 20 | period=0, 21 | connection_timeout=500, 22 | command_timeout=0); 23 | 24 | new db = redis.db( 25 | location="${redis_master1_ip}:${redis_master1_port}", 26 | type=master, 27 | connection_timeout=500, 28 | connection_ttl=0, 29 | command_timeout=0, 30 | max_command_retries=0, 31 | shared_connections=true, 32 | max_connections=1, 33 | password="", 34 | sickness_ttl=0, 35 | ignore_slaves=false, 36 | max_cluster_hops=0); 37 | db.add_server("${redis_slave1_1_ip}:${redis_slave1_1_port}", slave); 38 | db.add_server("${redis_slave1_2_ip}:${redis_slave1_2_port}", slave); 39 | } 40 | 41 | sub vcl_deliver { 42 | # INFO (master). 43 | db.command("INFO"); 44 | db.execute(true); 45 | if (db.reply_is_string()) { 46 | set resp.http.Reply-1 = regsub( 47 | db.get_string_reply(), 48 | "(?s)^.*\nrole:([^\s]+)\s.*$", "\1"); 49 | set resp.http.Reply-2 = regsub( 50 | db.get_string_reply(), 51 | "(?s)^.*\ntcp_port:([^\s]+)\s.*$", "\1"); 52 | } 53 | 54 | # INFO (slave). 55 | db.command("INFO"); 56 | db.execute(false); 57 | if (db.reply_is_string()) { 58 | set resp.http.Reply-3 = regsub( 59 | db.get_string_reply(), 60 | "(?s)^.*\nrole:([^\s]+)\s.*$", "\1"); 61 | set resp.http.Reply-4 = regsub( 62 | db.get_string_reply(), 63 | "(?s)^.*\ntcp_port:([^\s]+)\s.*$", "\1"); 64 | } 65 | 66 | # Stats. 67 | set resp.http.db-stats = db.stats(); 68 | set resp.http.db-servers-total = db.counter("servers.total"); 69 | set resp.http.db-connections-total = db.counter("connections.total"); 70 | set resp.http.db-commands-total = db.counter("commands.total"); 71 | set resp.http.db-commands-error = db.counter("commands.error"); 72 | } 73 | } -start 74 | 75 | client c1 { 76 | txreq 77 | rxresp 78 | 79 | expect resp.http.Reply-1 == "master" 80 | expect resp.http.Reply-2 == "${redis_master1_port}" 81 | 82 | expect resp.http.Reply-3 == "slave" 83 | expect resp.http.Reply-4 == "${redis_slave1_2_port}" 84 | 85 | expect resp.http.db-servers-total == "3" 86 | expect resp.http.db-connections-total == "2" 87 | expect resp.http.db-commands-total == "2" 88 | expect resp.http.db-commands-error == "0" 89 | } -run 90 | 91 | varnish v1 -expect client_req == 1 92 | -------------------------------------------------------------------------------- /src/tests/standalone.template.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Minimal test template" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -repeat 1 -start 7 | 8 | varnish v1 -arg "-p vsl_reclen=1024" -vcl+backend { 9 | import ${vmod_redis}; 10 | 11 | sub vcl_init { 12 | redis.subnets( 13 | masks={""}); 14 | 15 | redis.sentinels( 16 | locations={""}, 17 | period=0, 18 | connection_timeout=500, 19 | command_timeout=0); 20 | 21 | new db = redis.db( 22 | location="${redis_master1_ip}:${redis_master1_port}", 23 | type=master, 24 | connection_timeout=500, 25 | connection_ttl=0, 26 | command_timeout=0, 27 | max_command_retries=0, 28 | shared_connections=false, 29 | max_connections=1, 30 | password="", 31 | sickness_ttl=0, 32 | ignore_slaves=false, 33 | max_cluster_hops=0); 34 | } 35 | 36 | sub vcl_deliver { 37 | # Stats. 38 | set resp.http.db-stats = db.stats(); 39 | set resp.http.db-servers-total = db.counter("servers.total"); 40 | set resp.http.db-connections-total = db.counter("connections.total"); 41 | set resp.http.db-commands-total = db.counter("commands.total"); 42 | } 43 | } -start 44 | 45 | client c1 { 46 | txreq 47 | rxresp 48 | 49 | expect resp.http.db-servers-total == "1" 50 | expect resp.http.db-connections-total == "0" 51 | expect resp.http.db-commands-total == "0" 52 | } -run 53 | 54 | varnish v1 -expect client_req == 1 55 | --------------------------------------------------------------------------------