Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Policy chain - first version #450

Merged
merged 25 commits into from
Nov 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e658d90
prototype basic policy chain
mikz Oct 9, 2017
b1905a4
prototype of executor and context for sharing data
mikz Oct 10, 2017
9d8c59d
[policy] don't return metatable and let policy to figure it out
mikz Nov 3, 2017
db804e2
first version of truly local policy chain for every request
mikz Nov 3, 2017
80f5582
extract finding a service to a policy
mikz Nov 7, 2017
4cb932f
policies are loaded and initialized with service config
mikz Nov 7, 2017
c21b163
move phases definition to policy
mikz Nov 7, 2017
97b2056
spec: add tests for Policy
davidor Nov 9, 2017
7a40112
spec: add tests for PolicyChain
davidor Nov 9, 2017
d9c3bc6
spec: add tests for Executor
davidor Nov 10, 2017
d7db0df
spec: add tests for find_service policy
davidor Nov 10, 2017
c100bb4
spec: add tests for the load_configuration policy
davidor Nov 13, 2017
d5f1e6a
Add phase logger policy
davidor Nov 13, 2017
5f89a97
t: add basic integration test for policy chains
davidor Nov 13, 2017
83ed56d
spec: add tests for linked_list
davidor Nov 13, 2017
9dbb2a8
policy/local_chain: make explicit that the default chain contains onl…
davidor Nov 14, 2017
8075f69
linked_list: raise error when trying to modify read-only list
davidor Nov 14, 2017
34d5a94
policy: set version to 0.0 when not specified
davidor Nov 14, 2017
e1a63cb
policy: avoid exposing PHASES
davidor Nov 14, 2017
a6b9c94
executor: extract global_chain() and DEFAULT_POLICIES
davidor Nov 14, 2017
58b686f
spec/proxy: delete test that belongs to configuration_store
davidor Nov 14, 2017
5dc8c54
Document policy-related modules
davidor Nov 14, 2017
69ce941
policy_chain: delete unused add()
davidor Nov 14, 2017
a6b047a
policy_chain: simplify freeze of the chain
davidor Nov 14, 2017
95af30e
local_chain: keep compatibility with current module system
davidor Nov 14, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions apicast/conf.d/apicast.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ set_by_lua_block $deployment {
}

# TODO: enable in the future when we support SSL
# ssl_certificate_by_lua_block { require('module').call() }
# ssl_session_fetch_by_lua_block { require('module').call() }
# ssl_session_store_by_lua_block { require('module').call() }
# ssl_certificate_by_lua_block { require('executor').call() }
# ssl_session_fetch_by_lua_block { require('executor').call() }
# ssl_session_store_by_lua_block { require('executor').call() }

location = /___http_call {
internal;
Expand Down Expand Up @@ -48,11 +48,11 @@ location @out_of_band_authrep_action {

set_by_lua $original_request_time 'return ngx.var.request_time';

content_by_lua_block { require('module'):post_action() }
content_by_lua_block { require('executor'):post_action() }

log_by_lua_block {
ngx.var.post_action_impact = ngx.var.request_time - ngx.var.original_request_time
require('module'):log()
require('executor'):log()
}
}

Expand Down Expand Up @@ -81,12 +81,12 @@ location / {

proxy_ignore_client_abort on;

rewrite_by_lua_block { require('module'):rewrite() }
access_by_lua_block { require('module'):access() }
body_filter_by_lua_block { require('module'):body_filter() }
header_filter_by_lua_block { require('module'):header_filter() }
rewrite_by_lua_block { require('executor'):rewrite() }
access_by_lua_block { require('executor'):access() }
body_filter_by_lua_block { require('executor'):body_filter() }
header_filter_by_lua_block { require('executor'):header_filter() }

content_by_lua_block { require('module'):content() }
content_by_lua_block { require('executor'):content() }

proxy_pass $proxy_pass;
proxy_http_version 1.1;
Expand Down
4 changes: 2 additions & 2 deletions apicast/http.d/init.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ init_by_lua_block {
require("resty.core")
require('resty.resolver').init()

local module = require('module')
local module = require('executor')

if not module then
ngx.log(ngx.EMERG, 'fatal error when loading the root module')
Expand All @@ -21,7 +21,7 @@ init_by_lua_block {
}

init_worker_by_lua_block {
require('module'):init_worker()
require('executor'):init_worker()
}

lua_shared_dict init 16k;
2 changes: 1 addition & 1 deletion apicast/http.d/upstream.conf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
upstream upstream {
server 0.0.0.1:1;

balancer_by_lua_block { require('module'):balancer() }
balancer_by_lua_block { require('executor'):balancer() }

keepalive 1024;
}
Expand Down
27 changes: 9 additions & 18 deletions apicast/src/apicast.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
local proxy = require('proxy')
local balancer = require('balancer')
local math = math
local setmetatable = setmetatable

local configuration_loader = require('configuration_loader').new()
local configuration_store = require('configuration_store')
local user_agent = require('user_agent')

local noop = function() end
Expand All @@ -21,7 +18,6 @@ local mt = {
--- This is called when APIcast boots the master process.
function _M.new()
return setmetatable({
configuration = configuration_store.new(),
-- So there is no way to use ngx.ctx between request and post_action.
-- We somehow need to share the instance of the proxy between those.
-- This table is used to store the proxy object with unique reqeust id key
Expand All @@ -31,39 +27,33 @@ function _M.new()
}, mt)
end

function _M:init()
function _M.init()
user_agent.cache()

math.randomseed(ngx.now())
-- First calls to math.random after a randomseed tend to be similar; discard them
for _=1,3 do math.random() end

configuration_loader.init(self.configuration)
end

function _M:init_worker()
configuration_loader.init_worker(self.configuration)
function _M.init_worker()
end

function _M.cleanup()
-- now abort all the "light threads" running in the current request handler
ngx.exit(499)
end

function _M:rewrite()
ngx.on_abort(_M.cleanup)
function _M:rewrite(context)
ngx.on_abort(self.cleanup)

ngx.var.original_request_id = ngx.var.request_id

local host = ngx.var.host
-- load configuration if not configured
-- that is useful when lua_code_cache is off
-- because the module is reloaded and has to be configured again

local configuration = configuration_loader.rewrite(self.configuration, host)

local p = proxy.new(configuration)
p.set_upstream(p:set_service(host))
local p = context.proxy
p.set_upstream(context.service)
ngx.ctx.proxy = p
end

Expand All @@ -87,15 +77,16 @@ function _M:post_action()
end
end

function _M:access()
function _M:access(context)
local p = ngx.ctx.proxy
ngx.ctx.service = context.service
local post_action_proxy = self.post_action_proxy

if not post_action_proxy then
return nil, 'not initialized'
end

local access, handler = p:call() -- proxy:access() or oauth handler
local access, handler = p:call(context.service) -- proxy:access() or oauth handler

local ok, err

Expand Down
2 changes: 1 addition & 1 deletion apicast/src/balancer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local round_robin = require 'resty.balancer.round_robin'

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

function _M.call(_, balancer)
function _M.call(_, _, balancer)
balancer = balancer or _M.default_balancer
local host = ngx.var.proxy_host -- NYI: return to lower frame
local peers = balancer:peers(ngx.ctx[host])
Expand Down
14 changes: 14 additions & 0 deletions apicast/src/configuration.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ local re = require 'ngx.re'
local env = require 'resty.env'
local resty_url = require 'resty.url'
local util = require 'util'
local policy_chain = require 'policy_chain'

local mt = { __index = _M, __tostring = function() return 'Configuration' end }

Expand Down Expand Up @@ -157,6 +158,18 @@ local function backend_endpoint(proxy)
end
end

local function build_policy_chain(policies)
if not policies then return nil, 'no policy chain' end

local chain = {}

for i=1, #policies do
chain[i] = policy_chain.load(policies[i].name, policies[i].configuration)
end

return policy_chain.new(chain)
end

function _M.parse_service(service)
local backend_version = tostring(service.backend_version)
local proxy = service.proxy or empty_t
Expand All @@ -169,6 +182,7 @@ function _M.parse_service(service)
authentication_method = proxy.authentication_method or backend_version,
hosts = proxy.hosts or { 'localhost' }, -- TODO: verify localhost is good default
api_backend = proxy.api_backend,
policy_chain = build_policy_chain(proxy.policy_chain),
error_auth_failed = proxy.error_auth_failed or 'Authentication failed',
error_limits_exceeded = proxy.error_limits_exceeded or 'Limits exceeded',
error_auth_missing = proxy.error_auth_missing or 'Authentication parameters missing',
Expand Down
4 changes: 2 additions & 2 deletions apicast/src/configuration_loader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ local function ttl()
end

function _M.global(contents)
local module = require('module')
local context = require('executor'):context()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work when APICAST_MODULE is set.


return _M.configure(module.configuration, contents)
return _M.configure(context.configuration, contents)
end

function _M.configure(configuration, contents)
Expand Down
68 changes: 68 additions & 0 deletions apicast/src/executor.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
--- Executor module
-- The executor has a policy chain and will simply forward the calls it
-- receives to that policy chain. It also manages the 'context' that is passed
-- when calling the policy chain methods. This 'context' contains information
-- shared among policies.

local policy_chain = require('policy_chain')
local policy = require('policy')
local linked_list = require('linked_list')

local setmetatable = setmetatable

local _M = { }

local DEFAULT_POLICIES = {
'policy.load_configuration',
'policy.find_service',
'policy.local_chain'
}

local mt = { __index = _M }

-- forward all policy methods to the policy chain
for _,phase in policy.phases() do
_M[phase] = function(self, ...)
return self.policy_chain[phase](self.policy_chain, self:context(phase), ...)
end
end

local function global_chain()
return policy_chain.build(DEFAULT_POLICIES)
end

function _M.new()
return setmetatable({ policy_chain = global_chain() }, mt)
end

local function build_context(executor)
local config = executor.policy_chain:export()

return linked_list.readwrite({}, config)
end

local function shared_build_context(executor)
local ctx = ngx.ctx or {}
local context = ctx.context

if not context then
context = build_context(executor)

ctx.context = context
end

return context
end

--- Shared context among policies
-- @tparam string phase Nginx phase
-- @treturn linked_list The context. Note: The list returned is 'read-write'.
function _M:context(phase)
if phase == 'init' then
return build_context(self)
end

return shared_build_context(self)
end

return _M.new()
48 changes: 48 additions & 0 deletions apicast/src/linked_list.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
local setmetatable = setmetatable
local error = error

local _M = {

}

local noop = function() end


local empty_t = setmetatable({}, { __newindex = noop })
local __index = function(t,k)
return t.current[k] or t.next[k]
end

local ro_mt = {
__index = __index,
__newindex = function()
error("readonly list")
end,
}

local rw_mt = {
__index = __index,
__newindex = function(t, k, v)
t.current[k] = v
end
}

local function linked_list(item, next, mt)
return setmetatable({
current = item or empty_t,
next = next or empty_t
}, mt)
end

local function readonly_linked_list(item, next)
return linked_list(item, next, ro_mt)
end

local function readwrite_linked_list(item, next)
return linked_list(item, next, rw_mt)
end

_M.readonly = readonly_linked_list
_M.readwrite = readwrite_linked_list

return _M
12 changes: 6 additions & 6 deletions apicast/src/management.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
local _M = {}

local cjson = require('cjson')
local module = require('module')
local context = require('executor'):context()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work when APICAST_MODULE env is set.

local router = require('router')
local configuration_parser = require('configuration_parser')
local configuration_loader = require('configuration_loader')
Expand All @@ -28,7 +28,7 @@ function _M.live()
end

function _M.status(config)
local configuration = config or module.configuration
local configuration = config or context.configuration
-- TODO: this should be fixed for multi-tenant deployment
local has_configuration = configuration.configured
local has_services = #(configuration:all()) > 0
Expand All @@ -43,7 +43,7 @@ function _M.status(config)
end

function _M.config()
local config = module.configuration
local config = context.configuration
local contents = cjson.encode(config.configured and { services = config:all() } or nil)

ngx.header.content_type = 'application/json; charset=utf-8'
Expand All @@ -65,7 +65,7 @@ function _M.update_config()
local config, err = configuration_parser.decode(data)

if config then
local configured, error = configuration_loader.configure(module.configuration, config)
local configured, error = configuration_loader.configure(context.configuration, config)
-- TODO: respond with proper 304 Not Modified when config is the same
if configured and #(configured.services) > 0 then
json_response({ status = 'ok', config = config, services = #(configured.services)})
Expand All @@ -80,7 +80,7 @@ end
function _M.delete_config()
ngx.log(ngx.DEBUG, 'management config delete')

module.configuration:reset()
context.configuration:reset()
-- TODO: respond with proper 304 Not Modified when config is the same
local response = cjson.encode({ status = 'ok', config = cjson.null })
ngx.header.content_type = 'application/json; charset=utf-8'
Expand All @@ -96,7 +96,7 @@ function _M.boot()

ngx.log(ngx.DEBUG, 'management boot config:' .. inspect(data))

configuration_loader.configure(module.configuration, config)
configuration_loader.configure(context.configuration, config)

ngx.say(response)
end
Expand Down
Loading