Skip to content

Commit c890c39

Browse files
authored
Merge pull request #1025 from 3scale/upstream-config-policy
[THREESCALE-2166] Add Upstream Connection policy
2 parents e8214cd + 18698c9 commit c890c39

File tree

10 files changed

+234
-0
lines changed

10 files changed

+234
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1010

1111
- Ability to configure client certificate chain depth [PR #1006](https://github.com/3scale/APIcast/pull/1006)
1212
- You can filter services by endpoint name using Regexp [PR #1022](https://github.com/3scale/APIcast/pull/1022) [THREESCALE-1524](https://issues.jboss.org/browse/THREESCALE-1524)
13+
- "Upstream Connection" policy. It allows to configure several options for the connections to the upstream [PR #1025](https://github.com/3scale/APIcast/pull/1025), [THREESCALE-2166](https://issues.jboss.org/browse/THREESCALE-2166)
1314

1415
### Fixed
1516

gateway/src/apicast/balancer.lua

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
local assert = assert
22
local round_robin = require 'resty.balancer.round_robin'
33
local resty_url = require 'resty.url'
4+
local inspect = require 'inspect'
45

56
local _M = { default_balancer = round_robin.new() }
67

@@ -52,6 +53,23 @@ local function get_peer(balancer, upstream)
5253
return peer
5354
end
5455

56+
local function set_timeouts(balancer, timeouts)
57+
if not timeouts then return end
58+
59+
local _, err = balancer:set_timeouts(
60+
timeouts.connect_timeout,
61+
timeouts.send_timeout,
62+
timeouts.read_timeout
63+
)
64+
65+
if err then
66+
ngx.log(ngx.WARN,
67+
'Error while setting balancer timeouts: ',
68+
inspect(timeouts),
69+
' err: ', err)
70+
end
71+
end
72+
5573
function _M.call(_, context, bal)
5674
local balancer = assert(bal or _M.default_balancer, 'missing balancer')
5775
local upstream, peer, err, ok
@@ -71,6 +89,9 @@ function _M.call(_, context, bal)
7189
ok, err = balancer:set_current_peer(peer[1], peer[2] or upstream.uri.port or resty_url.default_port(upstream.uri.scheme))
7290

7391
if ok then
92+
-- context.upstream_connection_opts is set by the "upstream_connection" policy.
93+
set_timeouts(balancer, context.upstream_connection_opts)
94+
7495
-- I wish there would be a nicer way, but unfortunately ngx.exit(ngx.OK) does not
7596
-- terminate the current phase handler and will evaluate all remaining balancer phases.
7697
context[upstream] = peer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "http://apicast.io/policy-v1/schema#manifest#",
3+
"name": "Upstream connection",
4+
"summary": "Allows to configure several options for the connections to the upstream",
5+
"description": "Allows to configure several options for the connections to the upstream",
6+
"version": "builtin",
7+
"configuration": {
8+
"type": "object",
9+
"properties": {
10+
"connect_timeout": {
11+
"description": "Timeout for establishing a connection (in seconds).",
12+
"type": "integer"
13+
},
14+
"send_timeout": {
15+
"description": "Timeout between two successive write operations (in seconds).",
16+
"type": "number",
17+
"exclusiveMinimum": 0
18+
},
19+
"read_timeout": {
20+
"description": "Timeout between two successive read operations (in seconds).",
21+
"type": "number",
22+
"exclusiveMinimum": 0
23+
}
24+
}
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
return require('upstream_connection')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- Upstream Connection Policy
2+
--- This policy exposes some parameters related with the connection to the
3+
--- upstream.
4+
5+
local tonumber = tonumber
6+
7+
local _M = require('apicast.policy').new('Upstream connection policy')
8+
9+
local new = _M.new
10+
11+
function _M.new(config)
12+
local self = new(config)
13+
14+
self.connect_timeout = tonumber(config.connect_timeout)
15+
self.send_timeout = tonumber(config.send_timeout)
16+
self.read_timeout = tonumber(config.read_timeout)
17+
18+
return self
19+
end
20+
21+
function _M:export()
22+
return {
23+
upstream_connection_opts = {
24+
connect_timeout = self.connect_timeout,
25+
send_timeout = self.send_timeout,
26+
read_timeout = self.read_timeout
27+
}
28+
}
29+
end
30+
31+
return _M

gateway/src/resty/balancer.lua

+12
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,16 @@ function _M.set_peer(self, peers)
137137
end
138138
end
139139

140+
function _M:set_timeouts(connect_timeout, send_timeout, read_timeout)
141+
local ngx_balancer = self.balancer
142+
143+
if not ngx_balancer then
144+
return nil, 'balancer not available'
145+
end
146+
147+
-- If one of the values is nil, the default applies:
148+
-- https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md#set_timeouts
149+
return ngx_balancer.set_timeouts(connect_timeout, send_timeout, read_timeout)
150+
end
151+
140152
return _M

spec/balancer_spec.lua

+32
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,37 @@ describe('apicast.balancer', function()
2323
assert(apicast_balancer:call({ upstream = upstream }, balancer))
2424
assert.spy(balancer.set_current_peer).was.called_with(balancer, '127.0.0.2', 443)
2525
end)
26+
27+
it('sets the timeouts in the balancer when they are received in the context', function()
28+
local timeouts = {
29+
connect_timeout = 1,
30+
send_timeout = 2,
31+
read_timeout = 3,
32+
}
33+
34+
local balancer = setmetatable({
35+
set_current_peer = spy.new(function() return true end),
36+
set_timeouts = spy.new(function() return true end),
37+
}, { __index = apicast_balancer.default_balancer })
38+
39+
local upstream = Upstream.new('https://127.0.0.2')
40+
41+
upstream.servers = {
42+
{ address = '127.0.0.2', port = nil }
43+
}
44+
45+
apicast_balancer:call(
46+
{ upstream = upstream,
47+
upstream_connection_opts = timeouts },
48+
balancer
49+
)
50+
51+
assert.spy(balancer.set_timeouts).was_called_with(
52+
balancer,
53+
timeouts.connect_timeout,
54+
timeouts.send_timeout,
55+
timeouts.read_timeout
56+
)
57+
end)
2658
end)
2759
end)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
local UpstreamConnectionPolicy = require('apicast.policy.upstream_connection')
2+
3+
describe('Upstream connection policy', function()
4+
describe('.export', function()
5+
it('returns the timeouts included in the config', function()
6+
local config_timeouts = {
7+
connect_timeout = 1,
8+
send_timeout = 2,
9+
read_timeout = 3
10+
}
11+
local policy = UpstreamConnectionPolicy.new(config_timeouts)
12+
13+
local exported = policy:export()
14+
15+
assert.same(config_timeouts, exported.upstream_connection_opts)
16+
end)
17+
18+
it('does not return timeout params that is not in the config', function()
19+
local config_timeouts = { connect_timeout = 1 } -- Missing send and read
20+
local policy = UpstreamConnectionPolicy.new(config_timeouts)
21+
22+
local exported = policy:export()
23+
24+
assert.same(config_timeouts, exported.upstream_connection_opts)
25+
end)
26+
end)
27+
end)

spec/resty/balancer_spec.lua

+22
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,26 @@ describe('resty.balancer', function()
127127
end)
128128

129129
end)
130+
131+
describe('set_timeouts', function()
132+
it('forwards the call to the internal balancer', function()
133+
local b = resty_balancer.new(function() end)
134+
b.balancer = { } -- Internal balancer
135+
stub.new(b.balancer, 'set_timeouts', function() end)
136+
137+
b:set_timeouts(1, 2, 3)
138+
139+
assert.stub(b.balancer.set_timeouts).was_called_with(1, 2, 3)
140+
end)
141+
142+
it('returns an error when the internal balancer has not been set', function()
143+
local b = resty_balancer.new(function() end)
144+
b.balancer = nil -- Internal balancer
145+
146+
local res, err = b:set_timeouts(1, 2, 3)
147+
148+
assert.is_nil(res)
149+
assert.equals('balancer not available', err)
150+
end)
151+
end)
130152
end)
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use lib 't';
2+
use Test::APIcast::Blackbox 'no_plan';
3+
4+
run_tests();
5+
6+
__DATA__
7+
8+
=== TEST 1: Set timeouts
9+
In this test we set some timeouts to 1s. To force a read timeout, the upstream
10+
returns part of the response, then waits 3s (more than the timeout defined),
11+
and after that, it returns the rest of the response.
12+
This test uses the "ignore_response" section, because we know that the response
13+
is not going to be complete and that makes the Test::Nginx framework raise an
14+
error. With "ignore_response" that error is ignored.
15+
--- configuration
16+
{
17+
"services": [
18+
{
19+
"id": 42,
20+
"proxy": {
21+
"api_backend": "http://example.com:80/",
22+
"policy_chain": [
23+
{
24+
"name": "apicast.policy.upstream_connection",
25+
"configuration": {
26+
"connect_timeout": 1,
27+
"send_timeout": 1,
28+
"read_timeout": 1
29+
}
30+
},
31+
{
32+
"name": "apicast.policy.upstream",
33+
"configuration": {
34+
"rules": [
35+
{
36+
"regex": "/",
37+
"url": "http://test:$TEST_NGINX_SERVER_PORT"
38+
}
39+
]
40+
}
41+
}
42+
]
43+
}
44+
}
45+
]
46+
}
47+
--- upstream
48+
location / {
49+
content_by_lua_block {
50+
ngx.say("first part")
51+
ngx.flush(true)
52+
ngx.sleep(3)
53+
ngx.say("yay, second part")
54+
}
55+
}
56+
--- request
57+
GET /
58+
--- ignore_response
59+
--- error_log
60+
upstream timed out
61+
--- error_code:

0 commit comments

Comments
 (0)