diff --git a/apicast/conf.d/apicast.conf b/apicast/conf.d/apicast.conf index cb6cf79a6..e8eb4509a 100644 --- a/apicast/conf.d/apicast.conf +++ b/apicast/conf.d/apicast.conf @@ -5,9 +5,9 @@ set_by_lua_block $deployment { } # TODO: enable in the future when we support SSL -# ssl_certificate_by_lua_block { require('policy_chain').call() } -# ssl_session_fetch_by_lua_block { require('policy_chain').call() } -# ssl_session_store_by_lua_block { require('policy_chain').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 = /threescale_authrep { internal; @@ -39,12 +39,12 @@ location @out_of_band_authrep_action { set_by_lua $original_request_time 'return ngx.var.request_time'; - content_by_lua_block { require('policy_chain'):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 ngx.log(ngx.INFO, '[authrep] ', ngx.var.request_uri, ' ', ngx.var.status) - require('policy_chain'):log() + require('executor'):log() } } @@ -73,12 +73,12 @@ location / { proxy_ignore_client_abort on; - rewrite_by_lua_block { require('policy_chain'):rewrite() } - access_by_lua_block { require('policy_chain'):access() } - body_filter_by_lua_block { require('policy_chain'):body_filter() } - header_filter_by_lua_block { require('policy_chain'):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('policy_chain'):content() } + content_by_lua_block { require('executor'):content() } proxy_pass $proxy_pass; proxy_http_version 1.1; diff --git a/apicast/http.d/init.conf b/apicast/http.d/init.conf index dd734eaff..dd39e5431 100644 --- a/apicast/http.d/init.conf +++ b/apicast/http.d/init.conf @@ -8,7 +8,7 @@ init_by_lua_block { require("resty.core") require('resty.resolver').init() - local module = require('policy_chain') + local module = require('executor') if not module then ngx.log(ngx.EMERG, 'fatal error when loading the root module') @@ -21,7 +21,7 @@ init_by_lua_block { } init_worker_by_lua_block { - require('policy_chain'):init_worker() + require('executor'):init_worker() } lua_shared_dict init 16k; diff --git a/apicast/http.d/upstream.conf b/apicast/http.d/upstream.conf index c30b93181..43cf3ba1e 100644 --- a/apicast/http.d/upstream.conf +++ b/apicast/http.d/upstream.conf @@ -2,7 +2,7 @@ upstream upstream { server 0.0.0.1:1; - balancer_by_lua_block { require('policy_chain'):balancer() } + balancer_by_lua_block { require('executor'):balancer() } keepalive 1024; } @@ -10,7 +10,7 @@ upstream upstream { upstream backend_upstream { server 0.0.0.1:1; - balancer_by_lua_block { require('policy_chain'):balancer() } + balancer_by_lua_block { require('executor'):balancer() } keepalive 1024; } diff --git a/apicast/src/apicast.lua b/apicast/src/apicast.lua index bcc31fdc5..dc5645660 100644 --- a/apicast/src/apicast.lua +++ b/apicast/src/apicast.lua @@ -3,8 +3,6 @@ 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 @@ -21,7 +19,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 @@ -31,18 +28,15 @@ 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() @@ -50,20 +44,17 @@ function _M.cleanup() 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 = proxy.new(context.configuration) + p.set_upstream(p:set_service(context.host)) ngx.ctx.proxy = p end diff --git a/apicast/src/balancer.lua b/apicast/src/balancer.lua index b93485497..c85559539 100644 --- a/apicast/src/balancer.lua +++ b/apicast/src/balancer.lua @@ -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]) diff --git a/apicast/src/configuration_loader.lua b/apicast/src/configuration_loader.lua index 0d75d5afa..77ecb30bd 100644 --- a/apicast/src/configuration_loader.lua +++ b/apicast/src/configuration_loader.lua @@ -54,9 +54,9 @@ local function ttl() end function _M.global(contents) - local module = require('policy_chain') + local context = require('executor'):context() - return _M.configure(module.configuration, contents) + return _M.configure(context.configuration, contents) end function _M.configure(configuration, contents) diff --git a/apicast/src/executor.lua b/apicast/src/executor.lua new file mode 100644 index 000000000..e80787139 --- /dev/null +++ b/apicast/src/executor.lua @@ -0,0 +1,35 @@ +local setmetatable = setmetatable + +local policy_chain = require('policy_chain') +local linked_list = require('linked_list') + +local _M = { } + +local mt = { __index = _M } + +-- forward all policy methods to the policy chain +for i=1, #(policy_chain.PHASES) do + local phase_name = policy_chain.PHASES[i] + + _M[phase_name] = function(self, ...) + return self.policy_chain[phase_name](self.policy_chain, self:context(), ...) + end +end + +function _M.new() + local local_chain = policy_chain.build() + + local load_configuration = policy_chain.load('policy.load_configuration', local_chain) + + local global_chain = policy_chain.build({ load_configuration, local_chain }) + + return setmetatable({ policy_chain = global_chain }, mt) +end + +function _M:context() + local config = self.policy_chain:export() + + return linked_list.readwrite({}, config) +end + +return _M.new() diff --git a/apicast/src/linked_list.lua b/apicast/src/linked_list.lua new file mode 100644 index 000000000..607523356 --- /dev/null +++ b/apicast/src/linked_list.lua @@ -0,0 +1,45 @@ +local setmetatable = setmetatable + +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 = noop, +} + +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 diff --git a/apicast/src/management.lua b/apicast/src/management.lua index e089d4748..b521235e7 100644 --- a/apicast/src/management.lua +++ b/apicast/src/management.lua @@ -1,7 +1,7 @@ local _M = {} local cjson = require('cjson') -local module = require('policy_chain') +local context = require('executor'):context() local router = require('router') local configuration_parser = require('configuration_parser') local configuration_loader = require('configuration_loader') @@ -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 @@ -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' @@ -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)}) @@ -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' @@ -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 diff --git a/apicast/src/policy.lua b/apicast/src/policy.lua new file mode 100644 index 000000000..f8d210050 --- /dev/null +++ b/apicast/src/policy.lua @@ -0,0 +1,26 @@ +local _M = {} + +local setmetatable = setmetatable +local policy_chain = require('policy_chain') + +local noop = function() end + +function _M.new(name) + local policy = { + _NAME = name or 'policy', + _VERSION = '0.0', + } + local mt = { __index = policy } + + function policy.new() + return setmetatable({}, mt) + end + + for i=1,#(policy_chain.PHASES) do + policy[policy_chain.PHASES[i]] = noop + end + + return policy, mt +end + +return _M diff --git a/apicast/src/policy/load_configuration.lua b/apicast/src/policy/load_configuration.lua new file mode 100644 index 000000000..74571d8e7 --- /dev/null +++ b/apicast/src/policy/load_configuration.lua @@ -0,0 +1,33 @@ +local setmetatable = setmetatable + +local _M, mt = require('policy').new() + +local configuration_loader = require('configuration_loader').new() +local configuration_store = require('configuration_store') + +function _M.new() + return setmetatable({ + configuration = configuration_store.new(), + }, mt) +end + +function _M:export() + return { + configuration = self.configuration + } +end + +function _M:init() + configuration_loader.init(self.configuration) +end + +function _M:init_worker() + configuration_loader.init_worker(self.configuration) +end + +function _M:rewrite(context) + context.host = context.host or ngx.var.host + context.configuration = configuration_loader.rewrite(context.configuration, context.host) +end + +return _M diff --git a/apicast/src/policy_chain.lua b/apicast/src/policy_chain.lua index 911ea64cc..10dce43b8 100644 --- a/apicast/src/policy_chain.lua +++ b/apicast/src/policy_chain.lua @@ -5,45 +5,96 @@ if resty_env.get('APICAST_MODULE') then end local setmetatable = setmetatable +local insert = table.insert +local error = error +local rawset = rawset +local type = type +local require = require +local noop = function() end -local _M = { +local linked_list = require('linked_list') +local _M = { + PHASES = { + 'init', 'init_worker', + 'rewrite', 'access', 'balancer', + 'header_filter', 'body_filter', + 'post_action', 'log' + } } -local mt = { __index = _M } +local mt = { + __index = _M, + __newindex = function(t, k ,v) + if t.frozen then + error("readonly table") + else + rawset(t, k, v) + end + end +} function _M.build(modules) local chain = {} local list = modules or { 'apicast' } for i=1, #list do - chain[i] = require(list[i]).new() + chain[i] = _M.load(list[i]) end return _M.new(chain) end +function _M.load(module, ...) + if type(module) == 'string' then + return require(module).new(...) + else + return module + end +end + function _M.new(list) local chain = list or {} - return setmetatable(chain, mt) + local self = setmetatable(chain, mt) + chain.config = self:export() + return self:freeze() end -local phases = { - 'init', 'init_worker', - 'rewrite', 'access', 'balancer', - 'header_filter', 'body_filter', - 'post_action', 'log' -} +function _M:freeze() + self.frozen = true + return self +end -for i=1, #phases do - local phase_name = phases[i] +function _M:add(module) + insert(self, _M.load(module)) +end + +function _M:export() + local chain = self.config + + if chain then return chain end + + for i=#self, 1, -1 do + local export = self[i].export or noop + chain = linked_list.readonly(export(self[i]), chain) + end - _M[phase_name] = function(self, ...) + return chain +end + +local function call_chain(phase_name) + return function(self, ...) for i=1, #self do self[i][phase_name](self[i], ...) end end end +for i=1, #(_M.PHASES) do + local phase_name = _M.PHASES[i] + + _M[phase_name] = call_chain(phase_name) +end + return _M.build()