self optimizing routing code (#196)
* optimize lookups with a prefix tree
* fix tests
diff --git a/Makefile b/Makefile
index 0c4b37b..209eea1 100644
--- a/Makefile
+++ b/Makefile
@@ -26,6 +26,10 @@
-e TOKEN_FACEBOOK_URL=https://graph.facebook.com/debug_token \
-e TOKEN_GITHUB_URL=https://api.github.com/user \
-e DEBUG=true \
+ -e CACHING_ENABLED=true \
+ -e CACHE_SIZE=2048 \
+ -e CACHE_TTL=180 \
+ -e OPTIMIZE=1 \
-d openwhisk/apigateway-profiling:latest
.PHONY: test-run
diff --git a/api-gateway.conf b/api-gateway.conf
index 4c18873..3aea095 100644
--- a/api-gateway.conf
+++ b/api-gateway.conf
@@ -34,7 +34,7 @@
env HOST;
env PORT;
-
+env OPTIMIZE;
env REDIS_RETRY_COUNT;
env SNAPSHOTTING;
env CACHING_ENABLED;
diff --git a/conf.d/managed_endpoints.conf b/conf.d/managed_endpoints.conf
index ba9c94e..c7c5396 100644
--- a/conf.d/managed_endpoints.conf
+++ b/conf.d/managed_endpoints.conf
@@ -79,7 +79,7 @@
access_by_lua_block {
local routing = require "routing"
- local ds = require "lib/dataStore"
+ local ds = require "lib/dataStore"
routing.processCall(ds.init())
}
diff --git a/scripts/lua/cors.lua b/scripts/lua/cors.lua
index 3a22af1..50333c8 100644
--- a/scripts/lua/cors.lua
+++ b/scripts/lua/cors.lua
@@ -37,20 +37,20 @@
function _M.replaceHeaders()
if ngx.var.cors_origins ~= nil then
if ngx.var.cors_origins == 'true' then
- ngx.header['Access-Control-Allow-Headers'] = ngx.req.get_headers()['Access-Control-Request-Headers']
+ ngx.header['Access-Control-Allow-Headers'] = ngx.req.get_headers()['Access-Control-Request-Headers']
ngx.header['Access-Control-Allow-Origin'] = '*'
ngx.header['Access-Control-Allow-Methods'] = ngx.var.cors_methods
if ngx.var.cors_methods == nil then
ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS'
end
- elseif ngx.var.cors_origins == 'false' then
+ elseif ngx.var.cors_origins == 'false' then
ngx.header['Access-Control-Allow-Origin'] = nil
ngx.header['Access-Control-Allow-Methods'] = nil
else
ngx.header['Access-Control-Allow-Origin'] = ngx.var.cors_origins
ngx.header['Access-Control-Allow-Methods'] = ngx.var.cors_methods
- ngx.header['Access-Control-Allow-Headers'] = ngx.req.get_headers()['Access-Control-Request-Headers']
- if ngx.var.cors_methods == nil then
+ ngx.header['Access-Control-Allow-Headers'] = ngx.req.get_headers()['Access-Control-Request-Headers']
+ if ngx.var.cors_methods == nil then
ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS'
end
end
diff --git a/scripts/lua/lib/dataStore.lua b/scripts/lua/lib/dataStore.lua
index 764d870..85f209c 100644
--- a/scripts/lua/lib/dataStore.lua
+++ b/scripts/lua/lib/dataStore.lua
@@ -12,7 +12,6 @@
setmetatable(o, self)
self.__index = self
o.impl = require(utils.concatStrings({'lib/', DATASTORE}))
- o.ds = o.impl.init()
return o
end
@@ -38,28 +37,43 @@
end
function DataStore:close()
- return self.impl.close(self.ds)
+ if self.ds == nil then
+ return nil
+ else
+ return self.impl.close(self.ds)
+ end
end
function DataStore:addAPI(id, apiObj, existingAPI)
+ self:singleInit()
self:setSnapshotId(apiObj.tenantId)
return self.impl.addAPI(self.ds, id, apiObj, existingAPI)
end
function DataStore:getAllAPIs()
+ self:singleInit()
return self.impl.getAllAPIs(self.ds)
end
function DataStore:getAPI(id)
- return self.impl.getAPI(self.ds, id)
+ local result, ds = self.impl.getAPI(self.ds, id)
+ if ds ~= nil then
+ self.ds = ds
+ end
+ return result
end
function DataStore:deleteAPI(id)
+ self:singleInit()
return self.impl.deleteAPI(self.ds, id)
end
function DataStore:resourceToApi(resource)
- return self.impl.resourceToApi(self.ds, resource, self.snapshotId)
+ local result, ds = self.impl.resourceToApi(self.ds, resource, self.snapshotId)
+ if ds ~= nil then
+ self.ds = ds
+ end
+ return result
end
function DataStore:generateResourceObj(ops, apiId, tenantObj, cors)
@@ -67,89 +81,131 @@
end
function DataStore:createResource(key, field, resourceObj)
+ self:singleInit()
return self.impl.createResource(self.ds, key, field, resourceObj, self.snapshotId)
end
function DataStore:addResourceToIndex(index, resourceKey)
+ self:singleInit()
return self.impl.addResourceToIndex(self.ds, index, resourceKey, self.snapshotId)
end
function DataStore:deleteResourceFromIndex(index, resourceKey)
+ self:singleInit()
return self.impl.deleteResourceFromIndex(self.ds, index, resourceKey, self.snapshotId)
end
function DataStore:getResource(key, field)
+ self:singleInit()
return self.impl.getResource(self.ds, key, field, self.snapshotId)
end
function DataStore:getAllResources(tenantId)
+ self:singleInit()
return self.impl.getAllResources(self.ds, tenantId, self.snapshotId)
end
function DataStore:deleteResource(key, field)
+ self:singleInit()
return self.impl.deleteResource(self.ds, key, field, self.snapshotId)
end
function DataStore:addTenant(id, tenantObj)
+ self:singleInit()
return self.impl.addTenant(self.ds, id, tenantObj)
end
function DataStore:getAllTenants()
+ self:singleInit()
return self.impl.getAllTenants(self.ds)
end
function DataStore:getTenant(id)
- return self.impl.getTenant(self.ds, id)
+ local result, ds = self.impl.getTenant(self.ds, id)
+ if ds ~= nil then
+ self.ds = ds
+ end
+ return result
end
function DataStore:deleteTenant(id)
+ self:singleInit()
return self.impl.deleteTenant(self.ds, id)
end
function DataStore:createSubscription(key)
+ self:singleInit()
return self.impl.createSubscription(self.ds, key, self.snapshotId)
end
function DataStore:deleteSubscription(key)
+ self:singleInit()
return self.impl.deleteSubscription(self.ds, key, self.snapshotId)
end
function DataStore:getSubscriptions(artifactId, tenantId)
- return self.impl.getSubscriptions(artifactId, tenantId)
+ self:singleInit()
+ return self.impl.deleteSubscription(self.ds, key, self.snapshotId)
end
function DataStore:healthCheck()
+ self:singleInit()
return self.impl.healthCheck(self.ds)
end
function DataStore:addSwagger(id, swagger)
+ self:singleInit()
return self.impl.addSwagger(self.ds, id, swagger)
end
function DataStore:getSwagger(id)
+ self:singleInit()
return self.impl.getSwagger(self.ds, id)
end
function DataStore:deleteSwagger(id)
+ self:singleInit()
return self.impl.deleteSwagger(self.ds, id)
end
function DataStore:getOAuthToken(provider, token)
- return self.impl.getOAuthToken(self.ds, provider, token)
+ local result, ds = self.impl.getOAuthToken(self.ds, provider, token)
+ if ds ~= nil then
+ self.ds = ds
+ end
+ return result
end
function DataStore:saveOAuthToken(provider, token, body, ttl)
+ self:singleInit()
return self.impl.saveOAuthToken(self.ds, provider, token, body, ttl)
end
function DataStore:exists(key)
- return self.impl.exists(self.ds, key, self.snapshotId)
+ local result, ds = self.impl.exists(self.ds, key, self.snapshotId)
+ if ds ~= nil then
+ self.ds = ds
+ end
+ return result
end
function DataStore:setRateLimit(key, value, interval, expires)
+ self:singleInit()
return self.impl.setRateLimit(self.ds, key, value, interval, expires)
end
+
function DataStore:getRateLimit(key)
- return self.impl.getRateLimit(self.ds, key)
+ local result, ds = self.impl.getRateLimit(self.ds, key)
+ if ds ~= nil then
+ self.ds = ds
+ end
+ return result
end
--- o be removed in the future
+
+function DataStore:optimizedLookup(tenant, path)
+ local result, ds = self.impl.optimizedLookup(self.ds, tenant, path)
+ if ds ~= nil then
+ self.ds = ds
+ end
+ return result
+end
function DataStore:deleteSubscriptionAdv(artifactId, tenantId, clientId)
local subscriptionKey = utils.concatStrings({"subscriptions:tenant:", tenantId, ":api:", artifactId})
@@ -166,6 +222,19 @@
return true
end
+function DataStore:optimizeLookup(tenant, resourceKey, pathStr)
+ self:singleInit()
+ return self.impl.optimizeLookup(self.ds, tenant, resourceKey, pathStr)
+end
+
+
+function DataStore:singleInit()
+ if self.ds == nil then
+ self.ds = self.impl.init()
+ end
+end
+-- to be removed in the future
+
function _M.init()
return DataStore:init()
end
diff --git a/scripts/lua/lib/redis.lua b/scripts/lua/lib/redis.lua
index bfc24fc..65bc38c 100644
--- a/scripts/lua/lib/redis.lua
+++ b/scripts/lua/lib/redis.lua
@@ -29,6 +29,7 @@
local CACHE_SIZE
local CACHE_TTL
local c, err
+
local REDIS_HOST = os.getenv("REDIS_HOST")
local REDIS_PORT = os.getenv("REDIS_PORT")
local REDIS_PASS = os.getenv("REDIS_PASS")
@@ -66,7 +67,6 @@
local port = REDIS_PORT
local redis = require "resty.redis"
local red = redis:new()
-
red:set_timeout(REDIS_TIMEOUT)
-- Connect to Redis server
local retryCount = REDIS_RETRY_COUNT
@@ -534,14 +534,106 @@
return red:set(key, value, interval, expires)
end
+-- rate limiting is kind of special in that I don't want to get it from the cache because the intervals are too small.
+-- eventually may consider moving it into an nginx variable instead of redis
function _M.getRateLimit(red, key)
- return get(red, key)
+ if red == nil then
+ red = _M.init()
+ end
+ return red:get(key), red
+end
+
+function _M.optimizedLookup(red, tenant, path)
+ if CACHING_ENABLED then
+ local cached = c:get(utils.concatStrings({'fastmap:', tenant, ':', path}))
+ if cached ~= nil then
+ return cached
+ end
+ end
+ local script = [[
+ local tenant = ']] .. tenant .. [['
+ local path = ']] .. path .. [['
+ if redis.call('EXISTS', 'resources:' .. tenant .. ':' .. path) ~= 0 then
+ return 'resources:' .. tenant .. ':' .. path
+ end
+ local currStr = 'fastmap:' .. tenant
+ path = string.match(path, '[^?]*')
+ local exp_path = string.gmatch(path, '[^/]*')
+ local path = {}
+
+ for i in exp_path do
+ if i ~= nil and i ~= '' then
+ table.insert(path, i)
+ end
+ end
+
+ for i,v in ipairs(path) do
+ if redis.call('EXISTS', currStr .. '/' .. v) == 1 then
+ currStr = currStr .. '/' .. v
+ elseif redis.call('EXISTS', currStr .. '/.*') == 1 then
+ currStr = currStr .. '/.*'
+ else
+ return 0
+ end
+ end
+ return redis.call('GET', currStr)
+ ]]
+ if red == nil then
+ red = _M.init()
+ end
+ local result = red:eval(script, 0)
+ if type(result) ~= 'string' or result == '' then
+ return nil, red
+ end
+ ngx.var.gatewayPath = result:gsub(utils.concatStrings({'resources:', tenant, ':'}), '')
+
+ if CACHING_ENABLED then
+ c:set(utils.concatStrings({'fastmap:', tenant, ':', path}), result, CACHE_TTL)
+ end
+
+ return result, red
+end
+
+function _M.optimizeLookup(red, tenant, resourceKey, pathStr)
+ local startingString = utils.concatStrings({'fastmap:', tenant})
+ if get(red, startingString) == nil then
+ set(red, startingString, '')
+ end
+ path = {}
+ key = {}
+ for p in string.gmatch(pathStr, '[^/]*') do
+ if p ~= '' then
+ table.insert(path, p)
+ end
+ end
+
+ for r in string.gmatch(resourceKey:gsub('[^:]*:[^:]*:', ''), '[^/]*') do
+ if r ~= '' then
+ table.insert(key, r)
+ end
+ end
+
+ for i = 1, table.getn(path) do
+ if path[i] == key[i] then
+ startingString = utils.concatStrings({startingString, '/', key[i]})
+ if (exists(red, startingString)) == 0 then
+ set(red, startingString, '')
+ end
+ else
+ startingString = utils.concatStrings({startingString, '/.*'})
+ if (exists(red,startingString) == 0) then
+ set(red, startingString, '')
+ end
+ end
+ end
+ set(red, startingString, resourceKey)
end
function _M.lockSnapshot(red, snapshotId)
red:set(utils.concatStrings({'lock:snapshots:', snapshotId}), 'true')
red:expire(utils.concatStrings({'lock:snapshots:', snapshotId}), 60)
end
+
-- LRU Caching methods
function exists(red, key, snapshotId)
@@ -554,14 +646,20 @@
return 1
end
-- if it isn't in the cache, try and load it in there
+ if red == nil then
+ red = _M.init()
+ end
local result = red:get(key)
if result ~= ngx.null then
c:set(key, result, CACHE_TTL)
- return 1
+ return 1, red
end
return 0
else
- return red:exists(key)
+ if red == nil then
+ red = _M.init()
+ end
+ return red:exists(key), red
end
end
@@ -571,11 +669,17 @@
if cached ~= nil then
return cached
else
+ if red == nil then
+ red = _M.init()
+ end
local result = red:get(key)
c:set(key, result, CACHE_TTL)
- return result
+ return result, red
end
else
+ if red == nil then
+ red = _M.init()
+ end
return red:get(key)
end
end
@@ -588,20 +692,29 @@
if cached ~= nil then
return cached
else
+ if red == nil then
+ red = _M.init()
+ end
local result = red:hget(key, id)
cachedmap:set(id, result, CACHE_TTL)
c:set(key, cachedmap, CACHE_TTL)
- return result
+ return result, red
end
else
+ if red == nil then
+ red = _M.init()
+ end
local result = red:hget(key, id)
local newcache = lrucache.new(CACHE_SIZE)
newcache:set(id, result, CACHE_TTL)
c:set(key, newcache, CACHE_TTL)
- return result
+ return result, red
end
else
- return red:hget(key, id)
+ if red == nil then
+ red = _M.init()
+ end
+ return red:hget(key, id), red
end
end
diff --git a/scripts/lua/lib/utils.lua b/scripts/lua/lib/utils.lua
index 5a55710..d99192a 100644
--- a/scripts/lua/lib/utils.lua
+++ b/scripts/lua/lib/utils.lua
@@ -110,15 +110,15 @@
--- Takes a string and performs a SHA256 hash on its input
-- @param str the string to input into the hash function
-- @returns a hashed string
-function _Utils.hash(str)
+function _Utils.hash(str)
local resty_sha256 = require "resty.sha256"
- local resty_str = require "resty.string"
+ local resty_str = require "resty.string"
- local sha256 = resty_sha256:new()
+ local sha256 = resty_sha256:new()
sha256:update(str)
local digest = sha256:final()
return resty_str.to_hex(digest)
-end
+end
return _Utils
diff --git a/scripts/lua/oauth/facebook.lua b/scripts/lua/oauth/facebook.lua
index d1a5a81..2889f3f 100644
--- a/scripts/lua/oauth/facebook.lua
+++ b/scripts/lua/oauth/facebook.lua
@@ -21,26 +21,26 @@
local cjson = require 'cjson'
local utils = require "lib/utils"
-local _M = {}
+local _M = {}
function _M.process(dataStore, token)
local headerName = utils.concatStrings({'http_', 'x-facebook-app-token'}):gsub("-", "_")
-
+
local facebookAppToken = ngx.var[headerName]
if facebookAppToken == nil then
request.err(401, 'Facebook requires you provide an app token to validate user tokens. Provide a X-Facebook-App-Token header')
return nil
end
- local result = dataStore:getOAuthToken('facebook', token)
+ local result = dataStore:getOAuthToken('facebook', token)
if result ~= ngx.null then
return cjson.decode(result)
end
result = dataStore:getOAuthToken('facebook', utils.concatStrings({token, facebookAppToken}))
- if result ~= ngx.null then
- return cjson.decode(result)
+ if result ~= ngx.null then
+ return cjson.decode(result)
end
-
+
return exchangeOAuthToken(dataStore, token, facebookAppToken)
end
@@ -48,7 +48,7 @@
local http = require 'resty.http'
local request = require "lib/request"
local httpc = http.new()
-
+
local request_options = {
headers = {
['Accept'] = 'application/json'
diff --git a/scripts/lua/oauth/github.lua b/scripts/lua/oauth/github.lua
index 2335477..4b8f02c 100644
--- a/scripts/lua/oauth/github.lua
+++ b/scripts/lua/oauth/github.lua
@@ -21,18 +21,17 @@
-- A Proxy for Github OAuth API
local cjson = require "cjson"
local http = require "resty.http"
-local redis = require "lib/redis"
local request = require "lib/request"
local cjson = require "cjson"
local utils = require "lib/utils"
-local _M = {}
+local _M = {}
function _M.process(dataStore, token)
- local result = dataStore:getOAuthToken('github', token)
- if result ~= ngx.null then
+ local result = dataStore:getOAuthToken('github', token)
+ if result ~= ngx.null then
return cjson.decode(result)
- end
-
+ end
+
local request_options = {
headers = {
["Accept"] = "application/json"
@@ -52,7 +51,7 @@
request.err(500, 'OAuth provider error.')
return
end
-
+
local json_resp = cjson.decode(res.body)
if json_resp.id == nil then
return nil
@@ -62,10 +61,10 @@
return nil
end
- dataStore:saveOAuthToken('github', token)
+ dataStore:saveOAuthToken('github', token, cjson.encode(json_resp))
-- convert Github's response
-- Read more about the fields at: https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo
return json_resp
end
-return _M
+return _M
diff --git a/scripts/lua/oauth/mock.lua b/scripts/lua/oauth/mock.lua
index 2b88511..9de4a4d 100644
--- a/scripts/lua/oauth/mock.lua
+++ b/scripts/lua/oauth/mock.lua
@@ -20,7 +20,7 @@
-- DEALINGS IN THE SOFTWARE.
---
-- A fake oauth provider for testing
-local cjson = require "cjson"
+local cjson = require "cjson"
local _M = {}
function _M.process (red, token)
local result
diff --git a/scripts/lua/policies/rateLimit.lua b/scripts/lua/policies/rateLimit.lua
index 082824a..e59557d 100644
--- a/scripts/lua/policies/rateLimit.lua
+++ b/scripts/lua/policies/rateLimit.lua
@@ -26,7 +26,7 @@
local _M = {}
--- Limit a resource/api/tenant, based on the passed in rateLimit object
--- @param red redis client instance
+-- @param dataStore the datastore object
-- @param obj rateLimit object containing interval, rate, scope, subscription fields
-- @param apiKey optional api key to use if subscription is set to true
function limit(dataStore, obj, apiKey)
diff --git a/scripts/lua/policies/security.lua b/scripts/lua/policies/security.lua
index 0761f34..afc714d 100644
--- a/scripts/lua/policies/security.lua
+++ b/scripts/lua/policies/security.lua
@@ -27,6 +27,7 @@
local request = require "lib/request"
local utils = require "lib/utils"
--- Allow or block a request by calling a loaded security policy
+-- @param dataStore the datastore object
-- @param securityObj an object out of the security array in a given tenant / api / resource
function process(dataStore, securityObj)
local ok, result = pcall(require, utils.concatStrings({'policies/security/', securityObj.type}))
diff --git a/scripts/lua/policies/security/apiKey.lua b/scripts/lua/policies/security/apiKey.lua
index f462abb..d834dfa 100644
--- a/scripts/lua/policies/security/apiKey.lua
+++ b/scripts/lua/policies/security/apiKey.lua
@@ -33,8 +33,8 @@
local _M = {}
---- Validate that the given subscription is in redis
--- @param red a redis instance to query against
+--- Validate that the given subscription is in the datastore
+-- @param dataStore the datastore object
-- @param tenant the namespace
-- @param gatewayPath the gateway path to use, if scope is resource
-- @param apiId api Id to use, if scope is api
@@ -64,7 +64,7 @@
end
--- Process the security object
--- @param red a redis instance to query against
+-- @param dataStore the datastore object
-- @param securityObj security object from nginx conf file
-- @param hashFunction a function that will be called to hash the string
-- @return apiKey api key for the subscription
diff --git a/scripts/lua/policies/security/clientSecret.lua b/scripts/lua/policies/security/clientSecret.lua
index af865af..8fc221c 100644
--- a/scripts/lua/policies/security/clientSecret.lua
+++ b/scripts/lua/policies/security/clientSecret.lua
@@ -33,6 +33,7 @@
--- Process function main entry point for the security block.
-- Takes 2 headers and decides if the request should be allowed based on a hashed secret key
+--@param dataStore the datastore object
--@param securityObj the security object loaded from nginx.conf
--@return a string representation of what this looks like in redis :clientsecret:clientid:hashed secret
function process(dataStore, securityObj)
@@ -41,7 +42,7 @@
--- In order to properly test this functionallity, I use this function to do all of the business logic with injected dependencies
-- Takes 2 headers and decides if the request should be allowed based on a hashed secret key
--- @param red the redis instance to perform the lookup on
+-- @param dataStore the datastore object
-- @param securityObj the security configuration for the tenant/resource/api we are verifying
-- @param hashFunction the function used to perform the hash of the api secret
function processWithHashFunction(dataStore, securityObj, hashFunction)
@@ -93,7 +94,8 @@
return result
end
---- Validate that the subscription exists in redis
+--- Validate that the subscription exists in the dataStore
+-- @param dataStore the datastore object
-- @param tenant the tenantId we are checking for
-- @param gatewayPath the possible resource we are checking for
-- @param apiId if we are checking for an api
diff --git a/scripts/lua/policies/security/oauth2.lua b/scripts/lua/policies/security/oauth2.lua
index eebf536..778e7b4 100644
--- a/scripts/lua/policies/security/oauth2.lua
+++ b/scripts/lua/policies/security/oauth2.lua
@@ -63,6 +63,7 @@
end
--- Exchange tokens with an oauth provider. Loads a provider based on configuration in the nginx.conf
+-- @param dataStore the datastore object
-- @param token the accessToken passed in the authorization header of the routing request
-- @param provider the name of the provider we will load from a file. Currently supported google/github/facebook
-- @return the json object recieved from exchanging tokens with the provider
diff --git a/scripts/lua/routing.lua b/scripts/lua/routing.lua
index 09003c0..7bdf7d1 100644
--- a/scripts/lua/routing.lua
+++ b/scripts/lua/routing.lua
@@ -31,7 +31,13 @@
local rateLimit = require "policies/rateLimit"
local backendRouting = require "policies/backendRouting"
local cors = require "cors"
+local OPTIMIZE = os.getenv("OPTIMIZE")
+if OPTIMIZE ~= nil then
+ OPTIMIZE = tonumber(OPTIMIZE)
+else
+ OPTIMIZE = 0
+end
local SNAPSHOTTING = os.getenv('SNAPSHOTTING')
@@ -51,9 +57,7 @@
if ngx.req.get_headers()["x-debug-mode"] == "true" then
setRequestLogs()
end
- local resourceKeys = dataStore:getAllResources(tenantId)
- print(cjson.encode(resourceKeys))
- local redisKey = _M.findResource(resourceKeys, tenantId, gatewayPath)
+ local redisKey = _M.findResource(dataStore, tenantId, gatewayPath)
if redisKey == nil then
request.err(404, 'Not found.')
end
@@ -101,10 +105,10 @@
end
--- Find the correct redis key based on the path that's passed in
--- @param resourceKeys list of resourceKeys to search through
+-- @param dataStore the datastore object
-- @param tenant tenantId
-- @param path path to look for
-function _M.findResource(resourceKeys, tenant, path)
+function _M.findResource(dataStore, tenant, path)
-- Check for exact match
local redisKey = utils.concatStrings({"resources:", tenant, ":", path})
local cfRedisKey
@@ -117,6 +121,33 @@
ngx.var.analyticsUri = utils.concatStrings({ngx.var.analyticsUri, '?', u.query})
end
end
+
+ local result
+ if OPTIMIZE > 0 then
+ result = dataStore:optimizedLookup(tenant, path)
+ end
+ if result ~= nil then
+ ngx.var.gatewayPath = result:gsub(utils.concatStrings({'resources:', tenant, ':'}), '')
+ return result
+ end
+
+ local resourceKeys = dataStore:getAllResources(tenant)
+ local result = _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey)
+
+ if OPTIMIZE > 0 and result ~= nil then
+ dataStore:optimizeLookup(tenant, result, path)
+ end
+
+ return result
+end
+
+--- Perform a linear lookup of the api based on the apis in a tenant
+-- @param resourceKeys all of the resources under a given tenant
+-- @param tenant the tenant we are looking up for
+-- @param path the path used to call the api gateway
+-- @param redisKey a guess for a redis key based on the tenant id and path
+-- @param cfRedisKey a redis key that will exist if cloud foundry routing logic is used
+function _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey)
for _, key in pairs(resourceKeys) do
if key == redisKey or key == cfRedisKey then
local res = {string.match(key, "([^:]+):([^:]+):([^:]+)")}
@@ -163,6 +194,7 @@
redisKey = string.sub(redisKey, 1, index - 1)
end
return nil
+
end
--- Check redis if resourceKey matches path parameters
diff --git a/tests/scripts/lua/routing.lua b/tests/scripts/lua/routing.lua
index 2447d51..cb4c759 100644
--- a/tests/scripts/lua/routing.lua
+++ b/tests/scripts/lua/routing.lua
@@ -22,6 +22,8 @@
local fakeredis = require 'fakeredis'
local redis = require 'lib/redis'
local routing = require 'routing'
+local cjson = require 'cjson'
+
describe('Testing routing module', function()
before_each(function()
@@ -39,6 +41,7 @@
local field = 'resources'
for _, key in pairs(keys) do
red:hset(key, field, redis.generateResourceObj(operations, nil))
+ red:sadd('resources:guest:__index__', key)
end
end)
@@ -46,7 +49,8 @@
local expected = 'resources:guest:bp1/test/hello'
local tenant = 'guest'
local path = 'bp1/test/hello'
- local actual = routing.findResource(keys, tenant, path)
+ local ds = require 'lib/dataStore'
+ local actual = routing.findResource(ds.initWithDriver(red), tenant, path)
assert.are.same(expected, actual)
expected = 'bp1/test/hello'
actual = ngx.var.gatewayPath
@@ -57,8 +61,8 @@
local expected = nil
local tenant = 'guest'
local path = 'bp1/bad/path'
- local actual = routing.findResource(keys, tenant, path)
- print(actual)
+ local ds = require 'lib/dataStore'
+ local actual = routing.findResource(ds.initWithDriver(red), tenant, path)
assert.are.same(expected, actual)
end)
@@ -66,7 +70,8 @@
local expected = 'resources:guest:nobp'
local tenant = 'guest'
local path = 'nobp'
- local actual = routing.findResource(keys, tenant, path)
+ local ds = require 'lib/dataStore'
+ local actual = routing.findResource(ds.initWithDriver(red), tenant, path)
assert.are.same(expected, actual)
expected = 'nobp'
actual = ngx.var.gatewayPath
@@ -77,7 +82,8 @@
local expected = 'resources:guest:noresource/'
local tenant = 'guest'
local path = 'noresource/'
- local actual = routing.findResource(keys, tenant, path)
+ local ds = require 'lib/dataStore'
+ local actual = routing.findResource(ds.initWithDriver(red), tenant, path)
assert.are.same(expected, actual)
expected = 'noresource/'
actual = ngx.var.gatewayPath
@@ -131,8 +137,9 @@
local dataStore = ds.initWithDriver(red)
dataStore:setSnapshotId('test')
local result = dataStore:getAllResources('test')[1]
+ print ('etst')
assert.are.same(result, 'resources:test:v1/test')
local routing = require 'routing'
- local result = routing.findResource(dataStore:getAllResources('test'), 'test', 'v1/test')
+ local result = routing.findResource(dataStore, dataStore:getAllResources('test'), 'test', 'v1/test')
end)
end)