diff --git a/.github/workflows/ci-arm64.yml b/.github/workflows/ci-arm64.yml index 87e82fae14..a07ff1c835 100644 --- a/.github/workflows/ci-arm64.yml +++ b/.github/workflows/ci-arm64.yml @@ -68,6 +68,7 @@ jobs: --add-module=./modules/ngx_http_concat_module \ --add-module=./modules/ngx_http_footer_filter_module \ --add-module=./modules/ngx_http_lua_module \ + --add-module=./modules/ngx_http_lua_upstream \ --add-module=./modules/ngx_http_proxy_connect_module \ --add-module=./modules/ngx_http_reqstat_module \ --add-module=./modules/ngx_http_slice_module \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3eca95fc8f..5b25697fc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,7 @@ jobs: --add-module=./modules/ngx_http_concat_module \ --add-module=./modules/ngx_http_footer_filter_module \ --add-module=./modules/ngx_http_lua_module \ + --add-module=./modules/ngx_http_lua_upstream \ --add-module=./modules/ngx_http_proxy_connect_module \ --add-module=./modules/ngx_http_reqstat_module \ --add-module=./modules/ngx_http_slice_module \ diff --git a/.github/workflows/test-nginx-core.yml b/.github/workflows/test-nginx-core.yml index 735db7f744..a32c22bb47 100644 --- a/.github/workflows/test-nginx-core.yml +++ b/.github/workflows/test-nginx-core.yml @@ -109,6 +109,7 @@ jobs: --add-module=modules/ngx_http_concat_module \ --add-module=modules/ngx_http_footer_filter_module \ --add-module=modules/ngx_http_lua_module \ + --add-module=modules/ngx_http_lua_upstream \ --add-module=modules/ngx_http_proxy_connect_module \ --add-module=modules/ngx_http_reqstat_module \ --add-module=modules/ngx_http_sysguard_module \ diff --git a/.github/workflows/test-ntls.yml b/.github/workflows/test-ntls.yml index acde857f61..935a9c487d 100644 --- a/.github/workflows/test-ntls.yml +++ b/.github/workflows/test-ntls.yml @@ -75,6 +75,7 @@ jobs: --with-pcre \ --add-module=modules/ngx_tongsuo_ntls \ --add-module=modules/ngx_http_lua_module \ + --add-module=modules/ngx_http_lua_upstream \ --with-openssl=../Tongsuo \ --with-openssl-opt="--api=1.1.1 enable-ntls" \ --with-http_ssl_module \ diff --git a/modules/ngx_http_lua_upstream/.travis.yml b/modules/ngx_http_lua_upstream/.travis.yml new file mode 100644 index 0000000000..954f7f11ff --- /dev/null +++ b/modules/ngx_http_lua_upstream/.travis.yml @@ -0,0 +1,73 @@ +sudo: required +dist: focal + +os: linux + +language: c + +cache: + apt: true + directories: + - download-cache + +addons: + apt: + packages: + - axel + - libtest-base-perl + - libtext-diff-perl + - liburi-perl + - libwww-perl + - libtest-longstring-perl + - liblist-moreutils-perl + +compiler: + - gcc + +env: + global: + - LUAJIT_PREFIX=/opt/luajit21 + - LUAJIT_LIB=$LUAJIT_PREFIX/lib + - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH + - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 + - LUA_INCLUDE_DIR=$LUAJIT_INC + - LUA_CMODULE_DIR=/lib + - JOBS=3 + - NGX_BUILD_JOBS=$JOBS + matrix: + - NGINX_VERSION=1.21.4 + - NGINX_VERSION=1.25.1 + +services: + - redis-server + +install: + - echo $HOME + - if [ ! -d download-cache ]; then mkdir download-cache; fi + - if [ ! -f download-cache/ngx_http_redis-0.3.9.tar.gz ]; then wget -O download-cache/ngx_http_redis-0.3.9.tar.gz http://people.freebsd.org/~osa/ngx_http_redis-0.3.9.tar.gz; fi + - mkdir -p ~/work/nginx && cp download-cache/ngx_http_redis-0.3.9.tar.gz ~/work/nginx/ + - git clone https://github.com/openresty/nginx-devel-utils.git + - git clone https://github.com/openresty/openresty.git ../openresty + - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx + - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module + - git clone https://github.com/openresty/test-nginx.git + - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git + - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module + - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core + - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache + - git clone https://github.com/openresty/nginx-eval-module.git ../eval-nginx-module + - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module + - git clone https://github.com/openresty/set-misc-nginx-module.git ../set-misc-nginx-module + +script: + - cd luajit2 + - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' > build.log 2>&1 || (cat build.log && exit 1) + - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) + - cd .. + - cpanm --sudo ./test-nginx + - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH + - export NGX_BUILD_CC=$CC + - sh util/build.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1) + - nginx -V + - ldd `which nginx`|grep luajit + - prove -r t diff --git a/modules/ngx_http_lua_upstream/README.md b/modules/ngx_http_lua_upstream/README.md new file mode 100644 index 0000000000..d6383c3f2c --- /dev/null +++ b/modules/ngx_http_lua_upstream/README.md @@ -0,0 +1,337 @@ +Name +==== + +ngx_http_lua_upstream - Nginx C module to expose Lua API to ngx_lua for Nginx upstreams + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Functions](#functions) + * [get_upstreams](#get_upstreams) + * [get_servers](#get_servers) + * [get_primary_peers](#get_primary_peers) + * [get_backup_peers](#get_backup_peers) + * [set_peer_down](#set_peer_down) + * [current_upstream_name](#current_upstream_name) +* [TODO](#todo) +* [Compatibility](#compatibility) +* [Installation](#installation) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This module is production ready. + +Synopsis +======== + +```nginx +http { + upstream foo.com { + server 127.0.0.1 fail_timeout=53 weight=4 max_fails=100; + server agentzh.org:81; + } + + upstream bar { + server 127.0.0.2; + } + + server { + listen 8080; + + # sample output for the following /upstream interface: + # upstream foo.com: + # addr = 127.0.0.1:80, weight = 4, fail_timeout = 53, max_fails = 100 + # addr = 106.184.1.99:81, weight = 1, fail_timeout = 10, max_fails = 1 + # upstream bar: + # addr = 127.0.0.2:80, weight = 1, fail_timeout = 10, max_fails = 1 + + location = /upstreams { + default_type text/plain; + content_by_lua_block { + local concat = table.concat + local upstream = require "ngx.upstream" + local get_servers = upstream.get_servers + local get_upstreams = upstream.get_upstreams + + local us = get_upstreams() + for _, u in ipairs(us) do + ngx.say("upstream ", u, ":") + local srvs, err = get_servers(u) + if not srvs then + ngx.say("failed to get servers in upstream ", u) + else + for _, srv in ipairs(srvs) do + local first = true + for k, v in pairs(srv) do + if first then + first = false + ngx.print(" ") + else + ngx.print(", ") + end + if type(v) == "table" then + ngx.print(k, " = {", concat(v, ", "), "}") + else + ngx.print(k, " = ", v) + end + end + ngx.print("\n") + end + end + end + } + } + } +} +``` + +Functions +========= + +[Back to TOC](#table-of-contents) + +get_upstreams +------------- +`syntax: names = upstream.get_upstreams()` + +Get a list of the names for all the named upstream groups (i.e., explicit `upstream {}` blocks). + +Note that implicit upstream groups created by `proxy_pass` and etc are excluded. + +[Back to TOC](#table-of-contents) + +get_servers +----------- +`syntax: servers = upstream.get_servers(upstream_name)` + +Get configurations for all the servers in the specified upstream group. Please note that one server may take multiple addresses when its server name can be resolved to multiple addresses. + +The return value is an array-like Lua table. Each table entry is a hash-like Lua table that takes the following keys: + +* addr + + socket address(es). can be either a Lua string or an array-like Lua table of Lua strings. +* backup +* fail_timeout +* max_fails +* name +* weight + +[Back to TOC](#table-of-contents) + +get_primary_peers +----------------- +`syntax: peers = upstream.get_primary_peers(upstream_name)` + +Get configurations for all the primary (non-backup) peers in the specified upstream group. + +The return value is an array-like Lua table for all the primary peers. Each table entry is a (nested) hash-like Lua table that takes the following keys: + +* current_weight +* effective_weight +* fail_timeout +* fails +* id + + Identifier (ID) for the peer. This ID can be used to reference a peer in a group in the peer modifying API. +* max_fails +* name + + Socket address for the current peer +* weight +* accessed + + Timestamp for the last access (in seconds since the Epoch) +* checked + + Timestamp for the last check (in seconds since the Epoch) +* down + + Holds true if the peer has been marked as "down", otherwise this key is not present +* conns + + Number of active connections to the peer (this requires NGINX 1.9.0 or above). + +[Back to TOC](#table-of-contents) + +get_backup_peers +---------------- +`syntax: peers = upstream.get_backup_peers(upstream_name)` + +Get configurations for all the backup peers in the specified upstream group. + +The return value has the same structure as [get_primary_peers](#get_primary_peers) function. + +[Back to TOC](#table-of-contents) + +set_peer_down +------------- +`syntax: ok, err = upstream.set_peer_down(upstream_name, is_backup, peer_id, down_value)` + +Set the "down" (boolean) attribute of the specified peer. + +To uniquely specify a peer, you need to specify the upstream name, whether or not it is a backup peer, and the peer id (starting from 0). + +Note that this method only changes the peer settings in the current Nginx worker +process. You need to synchronize the changes across all the Nginx workers yourself if you +want a server-wide change (for example, by means of [ngx_lua](https://github.com/openresty/lua-nginx-module#ngxshareddict)'s [ngx.shared.DICT](https://github.com/openresty/lua-nginx-module#ngxshareddict)). + +Below is an example. Consider we have a "bar" upstream block in `nginx.conf`: + +```nginx +upstream bar { + server 127.0.0.2; + server 127.0.0.3 backup; + server 127.0.0.4 fail_timeout=23 weight=7 max_fails=200 backup; +} +``` + +then + +```lua +upstream.set_peer_down("bar", false, 0, true) +``` + +will turn down the primary peer corresponding to `server 127.0.0.2`. + +Similarly, + +```lua +upstream.set_peer_down("bar", true, 1, true) +``` + +will turn down the backup peer corresponding to `server 127.0.0.4 ...`. + +You can turn on a peer again by providing a `false` value as the 4th argument. + +[Back to TOC](#table-of-contents) + +current_upstream_name +--------------------- +`syntax: name = upstream.current_upstream_name()` + +Returns the name of the proxied upstream for the current request. +If there is no upstream for this request (no `proxy_pass` call), or this +function is called in a phase prior to the content phase, then the return value +will be `nil`. If a port is explicitly included in the upstream definition or +`proxy_pass` directive, it will be included in the return value of this function. + +Example: + +```lua +-- upstream my_upstream { ... } +-- proxy_pass http://my_upstream; +upstream.current_upstream_name() --> my_upstream + +-- proxy_pass http://example.com:1234; +upstream.current_upstream_name() --> example.com:1234 +``` + +Note that implicit upstreams created by `proxy_pass` are included, contrary to +the output of `upstream.get_upstreams()`. + +[Back to TOC](#table-of-contents) + +TODO +==== + +* Add API to add or remove servers to existing upstream groups. + +[Back to TOC](#table-of-contents) + +Compatibility +============= + +The following versions of Nginx should work with this module: + +* **1.11.x** (last tested: 1.11.2) +* **1.10.x** +* **1.9.x** (last tested: 1.9.15) +* **1.8.x** +* **1.7.x** (last tested: 1.7.10) +* **1.6.x** +* **1.5.x** (last tested: 1.5.12) + +[Back to TOC](#table-of-contents) + +Installation +============ + +This module is bundled and enabled by default in the [OpenResty](http://openresty.org) bundle. And you are recommended to use OpenResty. + +1. Grab the nginx source code from [nginx.org](http://nginx.org/), for example, +the version 1.11.2 (see [nginx compatibility](#compatibility)), +2. then grab the source code of the [ngx_lua](https://github.com/openresty/lua-nginx-module#installation) as well as its dependencies like [LuaJIT](http://luajit.org/download.html). +3. and finally build the source with this module: + +```bash +wget 'http://nginx.org/download/nginx-1.11.2.tar.gz' +tar -xzvf nginx-1.11.2.tar.gz +cd nginx-1.11.2/ + +# assuming your luajit is installed to /opt/luajit: +export LUAJIT_LIB=/opt/luajit/lib + +# assuming you are using LuaJIT v2.1: +export LUAJIT_INC=/opt/luajit/include/luajit-2.1 + +# Here we assume you would install you nginx under /opt/nginx/. +./configure --prefix=/opt/nginx \ + --with-ld-opt="-Wl,-rpath,$LUAJIT_LIB" \ + --add-module=/path/to/lua-nginx-module \ + --add-module=/path/to/lua-upstream-nginx-module + +make -j2 +make install +``` + +Starting from NGINX 1.9.11, you can also compile this module as a dynamic module, by using the `--add-dynamic-module=PATH` option instead of `--add-module=PATH` on the +`./configure` command line above. And then you can explicitly load the module in your `nginx.conf` via the [load_module](http://nginx.org/en/docs/ngx_core_module.html#load_module) +directive, for example, + +```nginx +load_module /path/to/modules/ngx_http_lua_upstream_module.so; +``` + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2014-2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: http://github.com/openresty/lua-nginx-module#readme +* the [lua-resty-upstream-healthcheck](https://github.com/openresty/lua-resty-upstream-healthcheck) library which makes use of the Lua API provided by this module. + +[Back to TOC](#table-of-contents) + diff --git a/modules/ngx_http_lua_upstream/config b/modules/ngx_http_lua_upstream/config new file mode 100644 index 0000000000..9e2bc0870e --- /dev/null +++ b/modules/ngx_http_lua_upstream/config @@ -0,0 +1,16 @@ +ngx_addon_name=ngx_http_lua_upstream_module +HTTP_LUA_UPSTREAM_SRCS="$ngx_addon_dir/src/ngx_http_lua_upstream_module.c" + +if test -n "$ngx_module_link"; then + ngx_module_type=HTTP + ngx_module_name=$ngx_addon_name + ngx_module_srcs="$HTTP_LUA_UPSTREAM_SRCS" + + . auto/module +else + HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" + NGX_ADDON_SRCS="$NGX_ADDON_SRCS $HTTP_LUA_UPSTREAM_SRCS" + + CORE_INCS="$CORE_INCS $ngx_module_incs" + CORE_LIBS="$CORE_LIBS $ngx_module_libs" +fi diff --git a/modules/ngx_http_lua_upstream/src/ddebug.h b/modules/ngx_http_lua_upstream/src/ddebug.h new file mode 100644 index 0000000000..dc1843077f --- /dev/null +++ b/modules/ngx_http_lua_upstream/src/ddebug.h @@ -0,0 +1,83 @@ + +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + + +#ifndef _DDEBUG_H_INCLUDED_ +#define _DDEBUG_H_INCLUDED_ + + +#include +#include +#include + + +#if defined(DDEBUG) && (DDEBUG) + +# if (NGX_HAVE_VARIADIC_MACROS) + +# define dd(...) fprintf(stderr, "lua-upstream *** %s: ", __func__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__) + +# else + +#include +#include + +#include + +static void dd(const char *fmt, ...) { +} + +# endif + +#else + +# if (NGX_HAVE_VARIADIC_MACROS) + +# define dd(...) + +# else + +#include + +static void dd(const char *fmt, ...) { +} + +# endif + +#endif + +#if defined(DDEBUG) && (DDEBUG) + +#define dd_check_read_event_handler(r) \ + dd("r->read_event_handler = %s", \ + r->read_event_handler == ngx_http_block_reading ? \ + "ngx_http_block_reading" : \ + r->read_event_handler == ngx_http_test_reading ? \ + "ngx_http_test_reading" : \ + r->read_event_handler == ngx_http_request_empty_handler ? \ + "ngx_http_request_empty_handler" : "UNKNOWN") + +#define dd_check_write_event_handler(r) \ + dd("r->write_event_handler = %s", \ + r->write_event_handler == ngx_http_handler ? \ + "ngx_http_handler" : \ + r->write_event_handler == ngx_http_core_run_phases ? \ + "ngx_http_core_run_phases" : \ + r->write_event_handler == ngx_http_request_empty_handler ? \ + "ngx_http_request_empty_handler" : "UNKNOWN") + +#else + +#define dd_check_read_event_handler(r) +#define dd_check_write_event_handler(r) + +#endif + + +#endif /* _DDEBUG_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/modules/ngx_http_lua_upstream/src/ngx_http_lua_upstream_module.c b/modules/ngx_http_lua_upstream/src/ngx_http_lua_upstream_module.c new file mode 100644 index 0000000000..6d128cb04f --- /dev/null +++ b/modules/ngx_http_lua_upstream/src/ngx_http_lua_upstream_module.c @@ -0,0 +1,652 @@ + +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#include +#include +#include +#include "ngx_http_lua_api.h" + + +ngx_module_t ngx_http_lua_upstream_module; + + +static ngx_int_t ngx_http_lua_upstream_init(ngx_conf_t *cf); +static int ngx_http_lua_upstream_create_module(lua_State * L); +static int ngx_http_lua_upstream_get_upstreams(lua_State * L); +static int ngx_http_lua_upstream_get_servers(lua_State * L); +static ngx_http_upstream_main_conf_t * + ngx_http_lua_upstream_get_upstream_main_conf(lua_State *L); +static int ngx_http_lua_upstream_get_primary_peers(lua_State * L); +static int ngx_http_lua_upstream_get_backup_peers(lua_State * L); +static int ngx_http_lua_get_peer(lua_State *L, + ngx_http_upstream_rr_peer_t *peer, ngx_uint_t id); +static ngx_http_upstream_srv_conf_t * + ngx_http_lua_upstream_find_upstream(lua_State *L, ngx_str_t *host); +static ngx_http_upstream_rr_peer_t * + ngx_http_lua_upstream_lookup_peer(lua_State *L); +static int ngx_http_lua_upstream_set_peer_down(lua_State * L); +static int ngx_http_lua_upstream_current_upstream_name(lua_State *L); + + +static ngx_http_module_t ngx_http_lua_upstream_ctx = { + NULL, /* preconfiguration */ + ngx_http_lua_upstream_init, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_lua_upstream_module = { + NGX_MODULE_V1, + &ngx_http_lua_upstream_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_http_lua_upstream_init(ngx_conf_t *cf) +{ + if (ngx_http_lua_add_package_preload(cf, "ngx.upstream", + ngx_http_lua_upstream_create_module) + != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static int +ngx_http_lua_upstream_create_module(lua_State * L) +{ + lua_createtable(L, 0, 6); + + lua_pushcfunction(L, ngx_http_lua_upstream_get_upstreams); + lua_setfield(L, -2, "get_upstreams"); + + lua_pushcfunction(L, ngx_http_lua_upstream_get_servers); + lua_setfield(L, -2, "get_servers"); + + lua_pushcfunction(L, ngx_http_lua_upstream_get_primary_peers); + lua_setfield(L, -2, "get_primary_peers"); + + lua_pushcfunction(L, ngx_http_lua_upstream_get_backup_peers); + lua_setfield(L, -2, "get_backup_peers"); + + lua_pushcfunction(L, ngx_http_lua_upstream_set_peer_down); + lua_setfield(L, -2, "set_peer_down"); + + lua_pushcfunction(L, ngx_http_lua_upstream_current_upstream_name); + lua_setfield(L, -2, "current_upstream_name"); + + return 1; +} + + +static int +ngx_http_lua_upstream_get_upstreams(lua_State * L) +{ + ngx_uint_t i; + ngx_http_upstream_srv_conf_t **uscfp, *uscf; + ngx_http_upstream_main_conf_t *umcf; + + if (lua_gettop(L) != 0) { + return luaL_error(L, "no argument expected"); + } + + umcf = ngx_http_lua_upstream_get_upstream_main_conf(L); + uscfp = umcf->upstreams.elts; + + lua_createtable(L, umcf->upstreams.nelts, 0); + + for (i = 0; i < umcf->upstreams.nelts; i++) { + + uscf = uscfp[i]; + + lua_pushlstring(L, (char *) uscf->host.data, uscf->host.len); + if (uscf->port) { + lua_pushfstring(L, ":%d", (int) uscf->port); + lua_concat(L, 2); + + /* XXX maybe we should also take "default_port" into account + * here? */ + } + + lua_rawseti(L, -2, i + 1); + } + + return 1; +} + + +static int +ngx_http_lua_upstream_get_servers(lua_State * L) +{ + ngx_str_t host; + ngx_uint_t i, j, n; + ngx_http_upstream_server_t *server; + ngx_http_upstream_srv_conf_t *us; + + if (lua_gettop(L) != 1) { + return luaL_error(L, "exactly one argument expected"); + } + + host.data = (u_char *) luaL_checklstring(L, 1, &host.len); + + us = ngx_http_lua_upstream_find_upstream(L, &host); + if (us == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "upstream not found"); + return 2; + } + + if (us->servers == NULL || us->servers->nelts == 0) { + lua_newtable(L); + return 1; + } + + server = us->servers->elts; + + lua_createtable(L, us->servers->nelts, 0); + + for (i = 0; i < us->servers->nelts; i++) { + + n = 4; + + if (server[i].name.len) { + n++; + } + + if (server[i].backup) { + n++; + } + + if (server[i].down) { + n++; + } + + lua_createtable(L, 0, n); + + if (server[i].name.len) { + lua_pushliteral(L, "name"); + lua_pushlstring(L, (char *) server[i].name.data, + server[i].name.len); + lua_rawset(L, -3); + } + + lua_pushliteral(L, "addr"); + + if (server[i].naddrs == 1) { + lua_pushlstring(L, (char *) server[i].addrs->name.data, + server[i].addrs->name.len); + + } else { + lua_createtable(L, server[i].naddrs, 0); + + for (j = 0; j < server[i].naddrs; j++) { + lua_pushlstring(L, (char *) server[i].addrs[j].name.data, + server[i].addrs[j].name.len); + lua_rawseti(L, -2, j + 1); + } + } + + lua_rawset(L, -3); + + lua_pushliteral(L, "weight"); + lua_pushinteger(L, (lua_Integer) server[i].weight); + lua_rawset(L, -3); + + lua_pushliteral(L, "max_fails"); + lua_pushinteger(L, (lua_Integer) server[i].max_fails); + lua_rawset(L, -3); + + lua_pushliteral(L, "fail_timeout"); + lua_pushinteger(L, (lua_Integer) server[i].fail_timeout); + lua_rawset(L, -3); + + if (server[i].backup) { + lua_pushliteral(L, "backup"); + lua_pushboolean(L, 1); + lua_rawset(L, -3); + } + + if (server[i].down) { + lua_pushliteral(L, "down"); + lua_pushboolean(L, 1); + lua_rawset(L, -3); + } + + lua_rawseti(L, -2, i + 1); + } + + return 1; +} + + +static int +ngx_http_lua_upstream_get_primary_peers(lua_State * L) +{ + ngx_str_t host; + ngx_uint_t i; + ngx_http_upstream_rr_peers_t *peers; + ngx_http_upstream_srv_conf_t *us; + + if (lua_gettop(L) != 1) { + return luaL_error(L, "exactly one argument expected"); + } + + host.data = (u_char *) luaL_checklstring(L, 1, &host.len); + + us = ngx_http_lua_upstream_find_upstream(L, &host); + if (us == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "upstream not found"); + return 2; + } + + peers = us->peer.data; + + if (peers == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "no peer data"); + return 2; + } + + lua_createtable(L, peers->number, 0); + + for (i = 0; i < peers->number; i++) { + ngx_http_lua_get_peer(L, &peers->peer[i], i); + lua_rawseti(L, -2, i + 1); + } + + return 1; +} + + +static int +ngx_http_lua_upstream_get_backup_peers(lua_State * L) +{ + ngx_str_t host; + ngx_uint_t i; + ngx_http_upstream_rr_peers_t *peers; + ngx_http_upstream_srv_conf_t *us; + + if (lua_gettop(L) != 1) { + return luaL_error(L, "exactly one argument expected"); + } + + host.data = (u_char *) luaL_checklstring(L, 1, &host.len); + + us = ngx_http_lua_upstream_find_upstream(L, &host); + if (us == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "upstream not found"); + return 2; + } + + peers = us->peer.data; + + if (peers == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "no peer data"); + return 2; + } + + peers = peers->next; + if (peers == NULL) { + lua_newtable(L); + return 1; + } + + lua_createtable(L, peers->number, 0); + + for (i = 0; i < peers->number; i++) { + ngx_http_lua_get_peer(L, &peers->peer[i], i); + lua_rawseti(L, -2, i + 1); + } + + return 1; +} + + +static int +ngx_http_lua_upstream_set_peer_down(lua_State * L) +{ + ngx_http_upstream_rr_peer_t *peer; + + if (lua_gettop(L) != 4) { + return luaL_error(L, "exactly 4 arguments expected"); + } + + peer = ngx_http_lua_upstream_lookup_peer(L); + if (peer == NULL) { + return 2; + } + + peer->down = lua_toboolean(L, 4); + + if (!peer->down) { + peer->fails = 0; + } + + lua_pushboolean(L, 1); + return 1; +} + + +static ngx_http_upstream_rr_peer_t * +ngx_http_lua_upstream_lookup_peer(lua_State *L) +{ + int id, backup; + ngx_str_t host; + ngx_http_upstream_srv_conf_t *us; + ngx_http_upstream_rr_peers_t *peers; + + host.data = (u_char *) luaL_checklstring(L, 1, &host.len); + + us = ngx_http_lua_upstream_find_upstream(L, &host); + if (us == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "upstream not found"); + return NULL; + } + + peers = us->peer.data; + + if (peers == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "no peer data"); + return NULL; + } + + backup = lua_toboolean(L, 2); + if (backup) { + peers = peers->next; + } + + if (peers == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "no backup peers"); + return NULL; + } + + id = luaL_checkint(L, 3); + if (id < 0 || (ngx_uint_t) id >= peers->number) { + lua_pushnil(L); + lua_pushliteral(L, "bad peer id"); + return NULL; + } + + return &peers->peer[id]; +} + + +static int +ngx_http_lua_get_peer(lua_State *L, ngx_http_upstream_rr_peer_t *peer, + ngx_uint_t id) +{ + ngx_uint_t n; + + n = 8; + +#if (nginx_version >= 1009000) + n++; +#endif + + if (peer->down) { + n++; + } + + if (peer->accessed) { + n++; + } + + if (peer->checked) { + n++; + } + + lua_createtable(L, 0, n); + + lua_pushliteral(L, "id"); + lua_pushinteger(L, (lua_Integer) id); + lua_rawset(L, -3); + + lua_pushliteral(L, "name"); + lua_pushlstring(L, (char *) peer->name.data, peer->name.len); + lua_rawset(L, -3); + + lua_pushliteral(L, "weight"); + lua_pushinteger(L, (lua_Integer) peer->weight); + lua_rawset(L, -3); + + lua_pushliteral(L, "current_weight"); + lua_pushinteger(L, (lua_Integer) peer->current_weight); + lua_rawset(L, -3); + + lua_pushliteral(L, "effective_weight"); + lua_pushinteger(L, (lua_Integer) peer->effective_weight); + lua_rawset(L, -3); + +#if (nginx_version >= 1009000) + lua_pushliteral(L, "conns"); + lua_pushinteger(L, (lua_Integer) peer->conns); + lua_rawset(L, -3); +#endif + + lua_pushliteral(L, "fails"); + lua_pushinteger(L, (lua_Integer) peer->fails); + lua_rawset(L, -3); + + lua_pushliteral(L, "max_fails"); + lua_pushinteger(L, (lua_Integer) peer->max_fails); + lua_rawset(L, -3); + + lua_pushliteral(L, "fail_timeout"); + lua_pushinteger(L, (lua_Integer) peer->fail_timeout); + lua_rawset(L, -3); + + if (peer->accessed) { + lua_pushliteral(L, "accessed"); + lua_pushinteger(L, (lua_Integer) peer->accessed); + lua_rawset(L, -3); + } + + if (peer->checked) { + lua_pushliteral(L, "checked"); + lua_pushinteger(L, (lua_Integer) peer->checked); + lua_rawset(L, -3); + } + + if (peer->down) { + lua_pushliteral(L, "down"); + lua_pushboolean(L, 1); + lua_rawset(L, -3); + } + + return 0; +} + + +static ngx_http_upstream_main_conf_t * +ngx_http_lua_upstream_get_upstream_main_conf(lua_State *L) +{ + ngx_http_request_t *r; + + r = ngx_http_lua_get_request(L); + + if (r == NULL) { + return ngx_http_cycle_get_module_main_conf(ngx_cycle, + ngx_http_upstream_module); + } + + return ngx_http_get_module_main_conf(r, ngx_http_upstream_module); +} + + +static ngx_http_upstream_srv_conf_t * +ngx_http_lua_upstream_find_upstream(lua_State *L, ngx_str_t *host) +{ + u_char *port; + size_t len; + ngx_int_t n; + ngx_uint_t i; + ngx_http_upstream_srv_conf_t **uscfp, *uscf; + ngx_http_upstream_main_conf_t *umcf; + + umcf = ngx_http_lua_upstream_get_upstream_main_conf(L); + +#if (NGX_HTTP_UPSTREAM_RBTREE) + + ngx_list_part_t *part; + + uscf = ngx_http_upstream_rbtree_lookup(umcf, host); + + if (uscf != NULL) { + return uscf; + } + + part = &umcf->implicit_upstreams.part; + uscfp = part->elts; + + for (i = 0; /* void */ ; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + uscfp = part->elts; + i = 0; + } + +#else + + uscfp = umcf->upstreams.elts; + + for (i = 0; i < umcf->upstreams.nelts; i++) { + +#endif + + uscf = uscfp[i]; + + if (uscf->host.len == host->len + && ngx_memcmp(uscf->host.data, host->data, host->len) == 0) + { + return uscf; + } + } + + port = ngx_strlchr(host->data, host->data + host->len, ':'); + if (port) { + port++; + n = ngx_atoi(port, host->data + host->len - port); + if (n < 1 || n > 65535) { + return NULL; + } + + /* try harder with port */ + + len = port - host->data - 1; + +#if (NGX_HTTP_UPSTREAM_RBTREE) + ngx_str_t addr; + addr.data = host->data; + addr.len = len; + uscf = ngx_http_upstream_rbtree_lookup(umcf, &addr); + if (uscf != NULL && uscf->port && uscf->port == n) + { + return uscf; + } + part = &umcf->implicit_upstreams.part; + uscfp = part->elts; + for (i = 0; /* void */ ; i++) { + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + part = part->next; + uscfp = part->elts; + i = 0; + } +#else + for (i = 0; i < umcf->upstreams.nelts; i++) { +#endif + + uscf = uscfp[i]; + + if (uscf->port + && uscf->port == n + && uscf->host.len == len + && ngx_memcmp(uscf->host.data, host->data, len) == 0) + { + return uscf; + } + } + } + + return NULL; +} + + +static int +ngx_http_lua_upstream_current_upstream_name(lua_State *L) +{ + ngx_http_request_t *r; + ngx_http_upstream_t *us; + ngx_http_upstream_conf_t *ucf; + ngx_http_upstream_srv_conf_t *uscf; + + r = ngx_http_lua_get_request(L); + if (r == NULL) { + return luaL_error(L, "no request object found"); + } + + us = r->upstream; + if (us == NULL) { + lua_pushnil(L); /* no proxying is being done */ + return 1; + } + + ucf = us->conf; + if (ucf == NULL) { + return luaL_error(L, "no conf for upstream"); + } + + uscf = ucf->upstream; + if (uscf == NULL) { + return luaL_error(L, "no srv conf for upstream"); + } + + lua_pushlstring(L, (char *) uscf->host.data, uscf->host.len); + + if (uscf->port) { + lua_pushfstring(L, ":%d", (int) uscf->port); + lua_concat(L, 2); + } + + return 1; +} diff --git a/modules/ngx_http_lua_upstream/t/count.t b/modules/ngx_http_lua_upstream/t/count.t new file mode 100644 index 0000000000..999385f668 --- /dev/null +++ b/modules/ngx_http_lua_upstream/t/count.t @@ -0,0 +1,43 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; + +$ENV{TEST_NGINX_MY_INIT_CONFIG} = <<_EOC_; +lua_package_path "t/lib/?.lua;;"; +_EOC_ + +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: number of entries in the module table +--- config + location /t { + content_by_lua_block { + local upstream = require "ngx.upstream" + local c = 0 + for _, _ in pairs(upstream) do + c = c + 1 + end + ngx.say("count: ", c) + } + } +--- request + GET /t +--- response_body +count: 6 +--- no_error_log +[error] diff --git a/modules/ngx_http_lua_upstream/t/lib/ljson.lua b/modules/ngx_http_lua_upstream/t/lib/ljson.lua new file mode 100644 index 0000000000..1084724935 --- /dev/null +++ b/modules/ngx_http_lua_upstream/t/lib/ljson.lua @@ -0,0 +1,89 @@ +local ngx_null = ngx.null +local tostring = tostring +local byte = string.byte +local gsub = string.gsub +local sort = table.sort +local pairs = pairs +local ipairs = ipairs +local concat = table.concat + +local ok, new_tab = pcall(require, "table.new") +if not ok then + new_tab = function (narr, nrec) return {} end +end + +local _M = {} + +local metachars = { + ['\t'] = '\\t', + ["\\"] = "\\\\", + ['"'] = '\\"', + ['\r'] = '\\r', + ['\n'] = '\\n', +} + +local function encode_str(s) + -- XXX we will rewrite this when string.buffer is implemented + -- in LuaJIT 2.1 because string.gsub cannot be JIT compiled. + return gsub(s, '["\\\r\n\t]', metachars) +end + +local function is_arr(t) + local exp = 1 + for k, _ in pairs(t) do + if k ~= exp then + return nil + end + exp = exp + 1 + end + return exp - 1 +end + +local encode + +encode = function (v) + if v == nil or v == ngx_null then + return "null" + end + + local typ = type(v) + if typ == 'string' then + return '"' .. encode_str(v) .. '"' + end + + if typ == 'number' or typ == 'boolean' then + return tostring(v) + end + + if typ == 'table' then + local n = is_arr(v) + if n then + local bits = new_tab(n, 0) + for i, elem in ipairs(v) do + bits[i] = encode(elem) + end + return "[" .. concat(bits, ",") .. "]" + end + + local keys = {} + local i = 0 + for key, _ in pairs(v) do + i = i + 1 + keys[i] = key + end + sort(keys) + + local bits = new_tab(0, i) + i = 0 + for _, key in ipairs(keys) do + i = i + 1 + bits[i] = encode(key) .. ":" .. encode(v[key]) + end + return "{" .. concat(bits, ",") .. "}" + end + + return '"<' .. typ .. '>"' +end +_M.encode = encode + +return _M diff --git a/modules/ngx_http_lua_upstream/t/sanity.t b/modules/ngx_http_lua_upstream/t/sanity.t new file mode 100644 index 0000000000..b913f21028 --- /dev/null +++ b/modules/ngx_http_lua_upstream/t/sanity.t @@ -0,0 +1,667 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; + +$ENV{TEST_NGINX_MY_INIT_CONFIG} = <<_EOC_; +lua_package_path "t/lib/?.lua;;"; +_EOC_ + +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: get upstream names +--- http_config + upstream foo.com:1234 { + server 127.0.0.1; + } + + upstream bar { + server 127.0.0.2; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local us = upstream.get_upstreams() + for _, u in ipairs(us) do + ngx.say(u) + end + ngx.say("done") + '; + } +--- request + GET /t +--- response_body +foo.com:1234 +bar +done +--- no_error_log +[error] + + + +=== TEST 2: get upstream names (no upstream) +--- http_config +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local us = upstream.get_upstreams() + for _, u in ipairs(us) do + ngx.say(u) + end + ngx.say("done") + '; + } +--- request + GET /t +--- response_body +done +--- no_error_log +[error] + + + +=== TEST 3: get servers +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + + upstream foo.com:1234 { + server 127.0.0.1 fail_timeout=53 weight=4 max_fails=100; + server agentzh.org:81 backup; + } + + upstream bar { + server 127.0.0.2; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + for _, host in pairs{ "foo.com:1234", "bar", "blah" } do + local srvs, err = upstream.get_servers(host) + if not srvs then + ngx.say("failed to get servers: ", err) + else + ngx.say(host, ": ", ljson.encode(srvs)) + end + end + '; + } +--- request + GET /t +--- response_body_like chomp +\Afoo\.com:1234: \[\{"addr":"127\.0\.0\.1:80","fail_timeout":53,"max_fails":100,"name":"127\.0\.0\.1","weight":4\},\{"addr":(?:\[?"\d+\.\d+\.\d+\.\d+:81",?\]?)+,"backup":true,"fail_timeout":10,"max_fails":1,"name":"agentzh\.org:81","weight":1\}\] +bar: \[\{"addr":"127\.0\.0\.2:80","fail_timeout":10,"max_fails":1,"name":"127\.0\.0\.2","weight":1\}\] +failed to get servers: upstream not found +\z +--- no_error_log +[error] + + + +=== TEST 4: sample in README +--- http_config + upstream foo.com { + server 127.0.0.1 fail_timeout=53 weight=4 max_fails=100; + server agentzh.org:81; + } + + upstream bar { + server 127.0.0.2; + } + +--- config + location = /upstreams { + default_type text/plain; + content_by_lua_block { + local concat = table.concat + local upstream = require "ngx.upstream" + local get_servers = upstream.get_servers + local get_upstreams = upstream.get_upstreams + + local us = get_upstreams() + for _, u in ipairs(us) do + ngx.say("upstream ", u, ":") + local srvs, err = get_servers(u) + if not srvs then + ngx.say("failed to get servers in upstream ", u) + else + for _, srv in ipairs(srvs) do + local first = true + local i = 0 + local keys = {} + for k, _ in pairs(srv) do + i = i + 1 + keys[i] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + local v = srv[k] + if first then + first = false + ngx.print(" ") + else + ngx.print(", ") + end + if type(v) == "table" then + ngx.print(k, " = {", concat(v, ", "), "}") + else + ngx.print(k, " = ", v) + end + end + ngx.print("\n") + end + end + end + } + } +--- request + GET /upstreams +--- response_body_like chomp +\Aupstream foo\.com: + addr = 127\.0\.0\.1:80, fail_timeout = 53, max_fails = 100, name = 127\.0\.0\.1, weight = 4 + addr = \{?(?:\d+\.\d+\.\d+\.\d+:81(?:,\s)?)+\}?, fail_timeout = 10, max_fails = 1, name = agentzh\.org:81, weight = 1 +upstream bar: + addr = 127\.0\.0\.2:80, fail_timeout = 10, max_fails = 1, name = 127\.0\.0\.2, weight = 1 +\z +--- no_error_log +[error] + + + +=== TEST 5: multi-peer servers +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + upstream test { + server multi-ip-test.openresty.com; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + local srvs, err = upstream.get_servers("test") + if not srvs then + ngx.say("failed to get test ", err) + return + end + ngx.say(ljson.encode(srvs)) + '; + } +--- request + GET /t +--- response_body_like chop +^\[\{"addr":\["\d{1,3}(?:\.\d{1,3}){3}:80"(?:,"\d{1,3}(?:\.\d{1,3}){3}:80")+\],"fail_timeout":10,"max_fails":1,"name":"multi-ip-test\.openresty\.com","weight":1\}\]$ + +--- no_error_log +[error] + + + +=== TEST 6: get primary peers: multi-peer servers +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + upstream test { + server multi-ip-test.openresty.com; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + local peers, err = upstream.get_primary_peers("test") + if not peers then + ngx.say("failed to get primary peers: ", err) + return + end + ngx.say(ljson.encode(peers)) + '; + } +--- request + GET /t +--- response_body_like chop +^\[\{"conns":0,"current_weight":0,"effective_weight":1,"fail_timeout":10,"fails":0,"id":0,"max_fails":1,"name":"\d{1,3}(?:\.\d{1,3}){3}:80","weight":1\}(?:,\{"conns":0,"current_weight":0,"effective_weight":1,"fail_timeout":10,"fails":0,"id":\d+,"max_fails":1,"name":"\d{1,3}(?:\.\d{1,3}){3}:80","weight":1\})+\]$ + +--- no_error_log +[error] + + + +=== TEST 7: get primary peers +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + upstream foo.com:1234 { + server 127.0.0.6 fail_timeout=5 backup; + server 127.0.0.1 fail_timeout=53 weight=4 max_fails=100; + server agentzh.org:81; + } + + upstream bar { + server 127.0.0.2; + server 127.0.0.3 backup; + server 127.0.0.4 fail_timeout=23 weight=7 max_fails=200 backup; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + us = upstream.get_upstreams() + for _, u in ipairs(us) do + local peers, err = upstream.get_primary_peers(u) + if not peers then + ngx.say("failed to get peers: ", err) + return + end + ngx.say(ljson.encode(peers)) + end + '; + } +--- request + GET /t +--- response_body_like chomp +\A\[\{"conns":0,"current_weight":0,"effective_weight":4,"fail_timeout":53,"fails":0,"id":0,"max_fails":100,"name":"127\.0\.0\.1:80","weight":4\},(?:\{"conns":0,"current_weight":0,"effective_weight":1,"fail_timeout":10,"fails":0,"id":\d+,"max_fails":1,"name":"\d+\.\d+\.\d+\.\d+:81","weight":1\},?)+\] +\[\{"conns":0,"current_weight":0,"effective_weight":1,"fail_timeout":10,"fails":0,"id":0,"max_fails":1,"name":"127\.0\.0\.2:80","weight":1\}\] +\z +--- no_error_log +[error] + + + +=== TEST 8: get backup peers +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + upstream foo.com:1234 { + server 127.0.0.6 fail_timeout=5 backup; + server 127.0.0.1 fail_timeout=53 weight=4 max_fails=100; + server agentzh.org:81; + } + + upstream bar { + server 127.0.0.2; + server 127.0.0.3 backup; + server 127.0.0.4 fail_timeout=23 weight=7 max_fails=200 backup; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + us = upstream.get_upstreams() + for _, u in ipairs(us) do + local peers, err = upstream.get_backup_peers(u) + if not peers then + ngx.say("failed to get peers: ", err) + return + end + ngx.say(ljson.encode(peers)) + end + '; + } +--- request + GET /t +--- response_body +[{"conns":0,"current_weight":0,"effective_weight":1,"fail_timeout":5,"fails":0,"id":0,"max_fails":1,"name":"127.0.0.6:80","weight":1}] +[{"conns":0,"current_weight":0,"effective_weight":1,"fail_timeout":10,"fails":0,"id":0,"max_fails":1,"name":"127.0.0.3:80","weight":1},{"conns":0,"current_weight":0,"effective_weight":7,"fail_timeout":23,"fails":0,"id":1,"max_fails":200,"name":"127.0.0.4:80","weight":7}] +--- no_error_log +[error] + + + +=== TEST 9: set primary peer down (0) +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + upstream bar { + server 127.0.0.2; + server 127.0.0.3 backup; + server 127.0.0.4 fail_timeout=23 weight=7 max_fails=200 backup; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + local u = "bar" + local ok, err = upstream.set_peer_down(u, false, 0, true) + if not ok then + ngx.say("failed to set peer down: ", err) + return + end + local peers, err = upstream.get_primary_peers(u) + if not peers then + ngx.say("failed to get peers: ", err) + return + end + ngx.say(ljson.encode(peers)) + '; + } +--- request + GET /t +--- response_body +[{"conns":0,"current_weight":0,"down":true,"effective_weight":1,"fail_timeout":10,"fails":0,"id":0,"max_fails":1,"name":"127.0.0.2:80","weight":1}] +--- no_error_log +[error] + + + +=== TEST 10: set primary peer down (1, bad index) +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + upstream bar { + server 127.0.0.2; + server 127.0.0.3 backup; + server 127.0.0.4 fail_timeout=23 weight=7 max_fails=200 backup; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + local u = "bar" + local ok, err = upstream.set_peer_down(u, false, 1, true) + if not ok then + ngx.say("failed to set peer down: ", err) + return + end + local peers, err = upstream.get_primary_peers(u) + if not peers then + ngx.say("failed to get peers: ", err) + return + end + ngx.say(ljson.encode(peers)) + '; + } +--- request + GET /t +--- response_body +failed to set peer down: bad peer id +--- no_error_log +[error] + + + +=== TEST 11: set backup peer down (index 0) +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + upstream bar { + server 127.0.0.2; + server 127.0.0.3 backup; + server 127.0.0.4 fail_timeout=23 weight=7 max_fails=200 backup; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + local u = "bar" + local ok, err = upstream.set_peer_down(u, true, 0, true) + if not ok then + ngx.say("failed to set peer down: ", err) + return + end + local peers, err = upstream.get_backup_peers(u) + if not peers then + ngx.say("failed to get peers: ", err) + return + end + ngx.say(ljson.encode(peers)) + '; + } +--- request + GET /t +--- response_body +[{"conns":0,"current_weight":0,"down":true,"effective_weight":1,"fail_timeout":10,"fails":0,"id":0,"max_fails":1,"name":"127.0.0.3:80","weight":1},{"conns":0,"current_weight":0,"effective_weight":7,"fail_timeout":23,"fails":0,"id":1,"max_fails":200,"name":"127.0.0.4:80","weight":7}] +--- no_error_log +[error] + + + +=== TEST 12: set backup peer down (toggle twice, index 0) +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + upstream bar { + server 127.0.0.2; + server 127.0.0.3 backup; + server 127.0.0.4 fail_timeout=23 weight=7 max_fails=200 backup; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + local u = "bar" + local ok, err = upstream.set_peer_down(u, true, 0, true) + if not ok then + ngx.say("failed to set peer down: ", err) + return + end + local ok, err = upstream.set_peer_down(u, true, 0, false) + if not ok then + ngx.say("failed to set peer down: ", err) + return + end + + local peers, err = upstream.get_backup_peers(u) + if not peers then + ngx.say("failed to get peers: ", err) + return + end + ngx.say(ljson.encode(peers)) + '; + } +--- request + GET /t +--- response_body +[{"conns":0,"current_weight":0,"effective_weight":1,"fail_timeout":10,"fails":0,"id":0,"max_fails":1,"name":"127.0.0.3:80","weight":1},{"conns":0,"current_weight":0,"effective_weight":7,"fail_timeout":23,"fails":0,"id":1,"max_fails":200,"name":"127.0.0.4:80","weight":7}] +--- no_error_log +[error] + + + +=== TEST 13: set backup peer down (index 1) +--- http_config + $TEST_NGINX_MY_INIT_CONFIG + upstream bar { + server 127.0.0.2; + server 127.0.0.3 backup; + server 127.0.0.4 fail_timeout=23 weight=7 max_fails=200 backup; + } +--- config + location /t { + content_by_lua ' + local upstream = require "ngx.upstream" + local ljson = require "ljson" + local u = "bar" + local ok, err = upstream.set_peer_down(u, true, 1, true) + if not ok then + ngx.say("failed to set peer down: ", err) + return + end + + local peers, err = upstream.get_backup_peers(u) + if not peers then + ngx.say("failed to get peers: ", err) + return + end + ngx.say(ljson.encode(peers)) + '; + } +--- request + GET /t +--- response_body +[{"conns":0,"current_weight":0,"effective_weight":1,"fail_timeout":10,"fails":0,"id":0,"max_fails":1,"name":"127.0.0.3:80","weight":1},{"conns":0,"current_weight":0,"down":true,"effective_weight":7,"fail_timeout":23,"fails":0,"id":1,"max_fails":200,"name":"127.0.0.4:80","weight":7}] +--- no_error_log +[error] + + + +=== TEST 14: upstream names with ports (github #2) +--- http_config +--- config + location /upstream1 { + proxy_pass http://127.0.0.1:1190; + } + + location /upstream2{ + proxy_pass http://127.0.0.2:1110; + } + + location /upstream3{ + proxy_pass http://127.0.0.1:1130; + } + + location /t { + content_by_lua_block { + local concat = table.concat + local upstream = require "ngx.upstream" + local get_servers = upstream.get_servers + local get_upstreams = upstream.get_upstreams + + local us = get_upstreams() + for _, u in ipairs(us) do + ngx.say("upstream ", u, ":") + local srvs, err = get_servers(u) + if not srvs then + ngx.say("failed to get servers in upstream ", u) + else + for _, srv in ipairs(srvs) do + local first = true + local i = 0 + local keys = {} + for k, _ in pairs(srv) do + i = i + 1 + keys[i] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + local v = srv[k] + if first then + first = false + ngx.print(" ") + else + ngx.print(", ") + end + if type(v) == "table" then + ngx.print(k, " = {", concat(v, ", "), "}") + else + ngx.print(k, " = ", v) + end + end + ngx.print("\n") + end + end + end + } + } +--- request + GET /t +--- response_body +upstream 127.0.0.1:1190: + addr = 127.0.0.1:1190, fail_timeout = 0, max_fails = 0, weight = 0 +upstream 127.0.0.2:1110: + addr = 127.0.0.2:1110, fail_timeout = 0, max_fails = 0, weight = 0 +upstream 127.0.0.1:1130: + addr = 127.0.0.1:1130, fail_timeout = 0, max_fails = 0, weight = 0 + +--- no_error_log +[error] + + + +=== TEST 15: upstream_name with valid explicit upstream +--- http_config + upstream some_upstream { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + } +--- config + log_by_lua_block { + local upstream = require "ngx.upstream" + ngx.log(ngx.INFO, "upstream = " .. tostring(upstream.current_upstream_name())) + } + location /test { + proxy_pass http://some_upstream/back; + } + location /back { + echo ok; + } +--- request +GET /test +--- response_body +ok +--- log_level: info +--- error_log eval +qr/upstream = some_upstream/ + + + +=== TEST 16: upstream_name with valid implicit upstream +--- config + log_by_lua_block { + local upstream = require "ngx.upstream" + ngx.log(ngx.INFO, "upstream = " .. tostring(upstream.current_upstream_name())) + } + location /test { + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/back; + } + location /back { + echo ok; + } +--- request +GET /test +--- response_body +ok +--- log_level: info +--- error_log eval +qr/upstream = 127.0.0.1:\d+/ + + + +=== TEST 17: upstream_name with no proxy_pass +--- config + log_by_lua_block { + local upstream = require "ngx.upstream" + ngx.log(ngx.INFO, "upstream = " .. tostring(upstream.current_upstream_name())) + } + location /test { + echo ok; + } +--- request +GET /test +--- response_body +ok +--- log_level: info +--- error_log eval +qr/upstream = nil/ + + + +=== TEST 18: upstream_name in content_by_lua +--- config + location /test { + content_by_lua_block { + local upstream = require "ngx.upstream" + ngx.say(upstream.current_upstream_name()) + } + } +--- request +GET /test +--- response_body +nil +--- no_error_log +[error] diff --git a/modules/ngx_http_lua_upstream/t/up-down.t b/modules/ngx_http_lua_upstream/t/up-down.t new file mode 100644 index 0000000000..9daa930dec --- /dev/null +++ b/modules/ngx_http_lua_upstream/t/up-down.t @@ -0,0 +1,67 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +#repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; + +$ENV{TEST_NGINX_MY_INIT_CONFIG} = <<_EOC_; +lua_package_path "t/lib/?.lua;;"; +_EOC_ + +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: turning a peer up should also clear fails +--- http_config + upstream foo.com { + server 127.0.0.1:12345 max_fails=3; + server 127.0.0.1:12346 max_fails=3; + } + + server { + server_name foo.com; + listen 12345; + listen 12346; + + location / { + return 444; + } + } + +--- config + location /t { + content_by_lua_block { + for i = 1, 3 do + ngx.location.capture("/sub") + end + local upstream = require "ngx.upstream" + local res = upstream.get_primary_peers("foo.com") + ngx.say(res[1].fails) + assert(upstream.set_peer_down("foo.com", false, 0, false)) + res = upstream.get_primary_peers("foo.com") + ngx.say(res[1].fails) + } + } + + location /sub { + proxy_pass http://foo.com; + } +--- request +GET /t +--- response_body +3 +0 +--- no_error_log +[alert] diff --git a/modules/ngx_http_lua_upstream/util/build.sh b/modules/ngx_http_lua_upstream/util/build.sh new file mode 100755 index 0000000000..9dedccc0aa --- /dev/null +++ b/modules/ngx_http_lua_upstream/util/build.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# this file is mostly meant to be used by the author himself. + +#set -x + +root=`pwd` +version=$1 +home=~ +force=$2 + +ngx_redis_version=0.3.9 +cd $home/work/nginx/ || exit 1 +ngx_redis_path=$home/work/nginx/ngx_http_redis-$ngx_redis_version +rm -rf $ngx_redis_path || exit 1 +tar -xzvf ngx_http_redis-$ngx_redis_version.tar.gz || exit 1 + +cd $ngx_redis_path || exit 1 + +patch_file=$root/../openresty/patches/ngx_http_redis-$ngx_redis_version-variables_in_redis_pass.patch +if [ ! -f $patch_file ]; then + echo "$patch_file: No such file" > /dev/stderr + exit 1 +fi +# we ignore any errors here since the target directory might have already been patched. +patch -p1 < $patch_file || exit 1 + +cd $ngx_redis_path || exit 1 + +patch_file=$root/../openresty/patches/ngx_http_redis-$ngx_redis_version-default_port_fix.patch +if [ ! -f $patch_file ]; then + echo "$patch_file: No such file" > /dev/stderr + exit 1 +fi +# we ignore any errors here since the target directory might have already been patched. +patch -p1 < $patch_file || exit 1 + +disable_pcre2=""; +patch_file=$root/../openresty/patches/ngx_http_redis-$ngx_redis_version-remove_content_encoding.patch +answer=`$root/../openresty/util/ver-ge "$version" 1.23.0` +if [ "$answer" = "Y" ]; then + disable_pcre2=--without-pcre2; + echo + echo "applying ngx_http_redis-$ver-remove_content_encoding.patch" + patch -p1 < $patch_file || exit 1 + echo +fi + +cd $root || exit 1 + + #--without-http_memcached_module \ +ngx-build $force $version \ + $disable_pcre2 \ + --with-cc-opt="-O0" \ + --with-ld-opt="-Wl,-rpath,/opt/postgres/lib:/opt/drizzle/lib:/usr/local/lib" \ + --without-mail_pop3_module \ + --without-mail_imap_module \ + --without-mail_smtp_module \ + --without-http_upstream_ip_hash_module \ + --without-http_empty_gif_module \ + --without-http_referer_module \ + --without-http_autoindex_module \ + --without-http_auth_basic_module \ + --without-http_userid_module \ + --add-module=$root/../ndk-nginx-module \ + --add-module=$root/../set-misc-nginx-module \ + --add-module=$ngx_redis_path \ + --add-module=$root/../echo-nginx-module \ + --add-module=$root $opts \ + --add-module=$root/../lua-nginx-module \ + --with-select_module \ + --with-poll_module \ + --with-debug + #--add-module=/home/agentz/git/dodo/utils/dodo-hook \ + #--add-module=$home/work/ngx_http_auth_request-0.1 #\ + #--with-rtsig_module + #--with-cc-opt="-g3 -O0" + #--add-module=$root/../echo-nginx-module \ + #--without-http_ssi_module # we cannot disable ssi because echo_location_async depends on it (i dunno why?!) + diff --git a/modules/ngx_http_lua_upstream/valgrind.suppress b/modules/ngx_http_lua_upstream/valgrind.suppress new file mode 100644 index 0000000000..9832c30b24 --- /dev/null +++ b/modules/ngx_http_lua_upstream/valgrind.suppress @@ -0,0 +1,151 @@ +{ + + Memcheck:Addr1 + fun:ngx_init_cycle + fun:ngx_master_process_cycle + fun:main +} +{ + + Memcheck:Addr4 + fun:ngx_init_cycle + fun:ngx_master_process_cycle + fun:main +} +{ + + Memcheck:Cond + fun:ngx_vslprintf + fun:ngx_snprintf + fun:ngx_sock_ntop + fun:ngx_event_accept + fun:ngx_epoll_process_events + fun:ngx_process_events_and_timers +} +{ + + Memcheck:Cond + fun:ngx_vslprintf + fun:ngx_snprintf + fun:ngx_sock_ntop + fun:ngx_event_accept + fun:ngx_epoll_process_events + fun:ngx_process_events_and_timers +} +{ + + Memcheck:Addr1 + fun:ngx_vslprintf + fun:ngx_snprintf + fun:ngx_sock_ntop + fun:ngx_event_accept +} +{ + + exp-sgcheck:SorG + fun:ngx_http_lua_ndk_set_var_get +} +{ + + exp-sgcheck:SorG + fun:ngx_http_variables_init_vars + fun:ngx_http_block +} +{ + + exp-sgcheck:SorG + fun:ngx_conf_parse +} +{ + + exp-sgcheck:SorG + fun:ngx_vslprintf + fun:ngx_log_error_core +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl +} +{ + + Memcheck:Cond + fun:ngx_conf_flush_files + fun:ngx_single_process_cycle +} +{ + + Memcheck:Cond + fun:memcpy + fun:ngx_vslprintf + fun:ngx_log_error_core + fun:ngx_http_charset_header_filter +} +{ + + Memcheck:Param + socketcall.setsockopt(optval) + fun:setsockopt + fun:drizzle_state_connect +} +{ + + Memcheck:Cond + fun:ngx_conf_flush_files + fun:ngx_single_process_cycle + fun:main +} +{ + + Memcheck:Leak + fun:malloc + fun:ngx_alloc + fun:ngx_event_process_init +} +{ + + Memcheck:Param + sendmsg(msg.msg_iov[0]) + fun:__sendmsg_nocancel + fun:ngx_write_channel + fun:ngx_pass_open_channel + fun:ngx_start_cache_manager_processes +} +{ + + Memcheck:Cond + fun:ngx_init_cycle + fun:ngx_master_process_cycle + fun:main +} +{ + + Memcheck:Cond + fun:index + fun:expand_dynamic_string_token + fun:_dl_map_object + fun:map_doit + fun:_dl_catch_error + fun:do_preload + fun:dl_main +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ngx_alloc + fun:ngx_set_environment + fun:ngx_single_process_cycle +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ngx_alloc + fun:ngx_set_environment + fun:ngx_worker_process_init + fun:ngx_worker_process_cycle +} diff --git a/tests/nginx-tests/tengine-tests/dynamic_resolve.t b/tests/nginx-tests/tengine-tests/dynamic_resolve.t index 262b3000a1..0c8dd5ce2d 100644 --- a/tests/nginx-tests/tengine-tests/dynamic_resolve.t +++ b/tests/nginx-tests/tengine-tests/dynamic_resolve.t @@ -127,8 +127,8 @@ foreach my $ip (@server_addrs) { $t->run_daemon(\&http_daemon, $ip); } -$t->run_daemon(\&dns_server_daemon); -my $dns_pid = pop @{$t->{_daemons}}; +my $dns = dns_server_daemon(); +$dns->start_server(60); $t->run(); @@ -142,9 +142,7 @@ like(http_get('/'), qr/127\.0\.0\.2/, # test variable in proxy_pass argument like(http_get('/proxy_pass_var'), qr/127\.0\.0\.2/, 'http server should be 127.0.0.2 for /proxy_pass_var'); - -# kill dns daemon -kill $^O eq 'MSWin32' ? 9 : 'TERM', $dns_pid; +$dns->stop_server(); wait; # wait for dns cache to expire @@ -258,7 +256,7 @@ sub dns_server_daemon { Verbose => 0 ) or die "couldn't create nameserver object\n"; - $ns->main_loop; + return $ns; } ############################################################################### diff --git a/tests/nginx-tests/tengine-tests/vnswrr4dynamic_ups.t b/tests/nginx-tests/tengine-tests/vnswrr4dynamic_ups.t index d0e17b1a06..7b71f0542f 100644 --- a/tests/nginx-tests/tengine-tests/vnswrr4dynamic_ups.t +++ b/tests/nginx-tests/tengine-tests/vnswrr4dynamic_ups.t @@ -132,8 +132,8 @@ foreach my $ip (@server_addrs) { $t->run_daemon(\&http_daemon, $ip); } -$t->run_daemon(\&dns_server_daemon); -my $dns_pid = pop @{$t->{_daemons}}; +my $dns = dns_server_daemon(); +$dns->start_server(60); $t->run(); @@ -148,8 +148,7 @@ like(http_get('/'), qr/127\.0\.0\.2/, like(http_get('/proxy_pass_var'), qr/127\.0\.0\.2/, 'http server should be 127.0.0.2 for /proxy_pass_var'); -# kill dns daemon -kill $^O eq 'MSWin32' ? 9 : 'TERM', $dns_pid; +$dns->stop_server(); wait; # wait for dns cache to expire @@ -255,7 +254,7 @@ sub dns_server_daemon { Verbose => 0 ) or die "couldn't create nameserver object\n"; - $ns->main_loop; + return $ns; } ###############################################################################