Merge pull request #11 from openwhisk/mgmt-update
Mgmt update
diff --git a/api-gateway-config/conf.d/management_apis.conf b/api-gateway-config/conf.d/management_apis.conf
index dc8c714..eccdcb1 100644
--- a/api-gateway-config/conf.d/management_apis.conf
+++ b/api-gateway-config/conf.d/management_apis.conf
@@ -33,19 +33,37 @@
access_log /var/log/api-gateway/access.log platform;
error_log /var/log/api-gateway/mgmt_error.log debug;
- location /resources {
+ location /v1/apis {
access_by_lua_block {
local mgmt = require("management")
local requestMethod = ngx.req.get_method()
if requestMethod == "GET" then
- mgmt.getResource()
+ mgmt.getAPIs()
elseif requestMethod == "PUT" then
- mgmt.addResource()
+ mgmt.addAPI()
elseif requestMethod == "POST" then
- ngx.status = 400
- ngx.say("Use PUT")
+ mgmt.addAPI()
elseif requestMethod == "DELETE" then
- mgmt.deleteResource()
+ mgmt.deleteAPI()
+ else
+ ngx.status = 400
+ ngx.say("Invalid verb")
+ end
+ }
+ }
+
+ location /v1/tenants {
+ access_by_lua_block {
+ local mgmt = require("management")
+ local requestMethod = ngx.req.get_method()
+ if requestMethod == "GET" then
+ mgmt.getTenants()
+ elseif requestMethod == "PUT" then
+ mgmt.addTenant()
+ elseif requestMethod == "POST" then
+ mgmt.addTenant()
+ elseif requestMethod == "DELETE" then
+ mgmt.deleteTenant()
else
ngx.status = 400
ngx.say("Invalid verb")
diff --git a/api-gateway-config/scripts/lua/lib/filemgmt.lua b/api-gateway-config/scripts/lua/lib/filemgmt.lua
index 13d508b..ca1d793 100644
--- a/api-gateway-config/scripts/lua/lib/filemgmt.lua
+++ b/api-gateway-config/scripts/lua/lib/filemgmt.lua
@@ -49,7 +49,6 @@
end
-- Add CORS headers
prefix = utils.concatStrings({prefix, "\tadd_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS';\n"})
-
-- Set resource headers and mapping by calling routing.processCall()
local outgoingResource = utils.concatStrings({
"\taccess_by_lua_block {\n",
diff --git a/api-gateway-config/scripts/lua/lib/redis.lua b/api-gateway-config/scripts/lua/lib/redis.lua
index f8144fc..62b2322 100644
--- a/api-gateway-config/scripts/lua/lib/redis.lua
+++ b/api-gateway-config/scripts/lua/lib/redis.lua
@@ -33,6 +33,10 @@
local _M = {}
+----------------------------
+-- Initialization/Cleanup --
+----------------------------
+
--- Initialize and connect to Redis
-- @param host redis host
-- @param port redis port
@@ -78,44 +82,83 @@
end
end
+---------------------------
+----------- APIs ----------
+---------------------------
+
+--- Add API to redis
+-- @param red Redis client instance
+-- @param id id of API
+-- @param apiObj the api to add
+function _M.addAPI(red, id, apiObj)
+ local ok, err = red:hset("apis", id, apiObj)
+ if not ok then
+ request.err(500, utils.concatStrings({"Failed to save the API: ", err}))
+ end
+end
+
+--- Get all APIs from redis
+-- @param red Redis client instance
+function _M.getAllAPIs(red)
+ local res, err = red:hgetall("apis")
+ if not res then
+ request.err(500, utils.concatStrings({"Failed to retrieve APIs: ", err}))
+ end
+ return res
+end
+
+--- Get a single API from redis given its id
+-- @param red Redis client instance
+-- @param id id of API to get
+function _M.getAPI(red, id)
+ local api, err = red:hget("apis", id)
+ if not api then
+ request.err(500, utils.concatStrings({"Failed to retrieve the API: ", err}))
+ end
+ if api == ngx.null then
+ return nil
+ end
+ return cjson.decode(api)
+end
+
+--- Delete an API from redis given its id
+-- @param red Redis client instance
+-- @param id id of API to delete
+function _M.deleteAPI(red, id)
+ local ok, err = red:hdel("apis", id)
+ if not ok then
+ request.err(500, utils.concatStrings({"Failed to delete the API: ", err}))
+ end
+end
+
+-----------------------------
+--------- Resources ---------
+-----------------------------
+
--- Generate Redis object for resource
--- @param red redis client instance
--- @param key redis resource key
--- @param gatewayMethod resource gateway method
--- @param backendUrl resource backend url
--- @param backendMethod resource backend method
+-- @param ops list of operations for a given resource
-- @param apiId resource api id (nil if no api)
--- @param policies list of policy objects
--- @param security security object
-function _M.generateResourceObj(red, key, gatewayMethod, backendUrl, backendMethod, apiId, policies, security)
- local newResource
- local resourceObj = _M.getResource(red, key, REDIS_FIELD)
- if resourceObj == nil then
- newResource = {
- operations = {
- [gatewayMethod] = {
- backendUrl = backendUrl,
- backendMethod = backendMethod,
- }
- }
+function _M.generateResourceObj(ops, apiId)
+ local resourceObj = {
+ operations = {}
+ }
+ for op, v in pairs(ops) do
+ op = op:upper()
+ resourceObj.operations[op] = {
+ backendUrl = v.backendUrl,
+ backendMethod = v.backendMethod
}
- else
- newResource = cjson.decode(resourceObj)
- newResource.operations[gatewayMethod] = {
- backendUrl = backendUrl,
- backendMethod = backendMethod,
- }
+ if v.policies then
+ resourceObj.operations[op].policies = v.policies
+ end
+ if v.security then
+ resourceObj.operations[op].security = v.security
+ end
end
if apiId then
- newResource.apiId = apiId
+ resourceObj.apiId = apiId
end
- if policies then
- newResource.operations[gatewayMethod].policies = policies
- end
- if security then
- newResource.operations[gatewayMethod].security = security
- end
- return cjson.encode(newResource)
+ return cjson.encode(resourceObj)
end
--- Create/update resource in redis
@@ -127,7 +170,7 @@
-- Add/update resource to redis
local ok, err = red:hset(key, field, resourceObj)
if not ok then
- request.err(500, utils.concatStrings({"Failed adding resource to redis: ", err}))
+ request.err(500, utils.concatStrings({"Failed to save the resource: ", err}))
end
end
@@ -139,7 +182,7 @@
function _M.getResource(red, key, field)
local resourceObj, err = red:hget(key, field)
if not resourceObj then
- request.err(500, utils.concatStrings({"Failed getting resource: ", err}))
+ request.err(500, utils.concatStrings({"Failed to retrieve the resource: ", err}))
end
-- return nil if resource doesn't exist
if resourceObj == ngx.null then
@@ -149,26 +192,79 @@
return resourceObj
end
---- Delete resource int redis
+--- Delete resource in redis
-- @param red redis client instance
-- @param key redis resource key
-- @param field redis resource field
function _M.deleteResource(red, key, field)
local resourceObj, err = red:hget(key, field)
if not resourceObj then
- request.err(500, utils.concatStrings({"Failed deleting resource: ", err}))
+ request.err(500, utils.concatStrings({"Failed to delete the resource: ", err}))
end
if resourceObj == ngx.null then
request.err(404, "Resource doesn't exist.")
end
local ok, err = red:del(key)
if not ok then
- request.err(500, utils.concatStrings({"Failed deleting resource: ", err}))
+ request.err(500, utils.concatStrings({"Failed to delete the resource: ", err}))
else
return ok
end
end
+-----------------------------
+---------- Tenants ----------
+-----------------------------
+
+--- Add tenant to redis
+-- @param red Redis client instance
+-- @param id id of tenant
+-- @param tenantObj the tenant to add
+function _M.addTenant(red, id, tenantObj)
+ local ok, err = red:hset("tenants", id, tenantObj)
+ if not ok then
+ request.err(500, utils.concatStrings({"Failed to add the tenant: ", err}))
+ end
+end
+
+--- Get all tenants from redis
+-- @param red Redis client instance
+function _M.getAllTenants(red)
+ local res, err = red:hgetall("tenants")
+ if not res then
+ request.err(500, utils.concatStrings({"Failed to retrieve tenants: ", err}))
+ end
+ return res
+end
+
+--- Get a single tenant from redis given its id
+-- @param red Redis client instance
+-- @param id id of tenant to get
+function _M.getTenant(red, id)
+ local tenant, err = red:hget("tenants", id)
+ if not tenant then
+ request.err(500, utils.concatStrings({"Failed to retrieve the tenant: ", err}))
+ end
+ if tenant == ngx.null then
+ return nil
+ end
+ return cjson.decode(tenant)
+end
+
+--- Delete an tenant from redis given its id
+-- @param red Redis client instance
+-- @param id id of tenant to delete
+function _M.deleteTenant(red, id)
+ local ok, err = red:hdel("tenants", id)
+ if not ok then
+ request.err(500, utils.concatStrings({"Failed to delete the tenant: ", err}))
+ end
+end
+
+-----------------------------
+--- API Key Subscriptions ---
+-----------------------------
+
--- Create/update subscription/apikey in redis
-- @param red redis client instance
-- @param key redis subscription key to create
@@ -176,7 +272,7 @@
-- Add/update a subscription key to redis
local ok, err = red:set(key, '')
if not ok then
- request.err(500, utils.concatStrings({"Failed adding subscription to redis", err}))
+ request.err(500, utils.concatStrings({"Failed to add the subscription key", err}))
end
end
@@ -186,17 +282,21 @@
function _M.deleteSubscription(red, key)
local subscription, err = red:get(key)
if not subscription then
- request.err(500, utils.concatStrings({"Failed to delete subscription: ", err}))
+ request.err(500, utils.concatStrings({"Failed to delete the subscription key: ", err}))
end
if subscription == ngx.null then
request.err(404, "Subscription doesn't exist.")
end
local ok, err = red:del(key)
if not ok then
- request.err(500, utils.concatStrings({"Failed to delete subscription: ", err}))
+ request.err(500, utils.concatStrings({"Failed to delete the subscription key: ", err}))
end
end
+-----------------------------------
+------- Pub/Sub with Redis --------
+-----------------------------------
+
--- Subscribe to redis
-- @param redisSubClient the redis client that is listening for the redis key changes
-- @param redisGetClient the redis client that gets the changed resource to update the conf file
@@ -316,4 +416,4 @@
ngx.exit(ngx.status)
end
-return _M
+return _M
\ No newline at end of file
diff --git a/api-gateway-config/scripts/lua/lib/utils.lua b/api-gateway-config/scripts/lua/lib/utils.lua
index 3d2cc10..d1c1cf1 100644
--- a/api-gateway-config/scripts/lua/lib/utils.lua
+++ b/api-gateway-config/scripts/lua/lib/utils.lua
@@ -74,9 +74,20 @@
return concatStrings({"(?<path_" , x , ">([a-zA-Z0-9\\-\\s\\_\\%]*))"})
end
+--- Generate random uuid
+function uuid()
+ local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
+ math.randomseed(os.time())
+ return string.gsub(template, '[xy]', function (c)
+ local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
+ return string.format('%x', v)
+ end)
+end
+
_Utils.concatStrings = concatStrings
_Utils.serializeTable = serializeTable
_Utils.convertTemplatedPathParam = convertTemplatedPathParam
+_Utils.uuid = uuid
return _Utils
diff --git a/api-gateway-config/scripts/lua/management.lua b/api-gateway-config/scripts/lua/management.lua
index 929fbd2..0bc3357 100644
--- a/api-gateway-config/scripts/lua/management.lua
+++ b/api-gateway-config/scripts/lua/management.lua
@@ -28,127 +28,460 @@
local utils = require "lib/utils"
local logger = require "lib/logger"
local request = require "lib/request"
-
local REDIS_HOST = os.getenv("REDIS_HOST")
local REDIS_PORT = os.getenv("REDIS_PORT")
local REDIS_PASS = os.getenv("REDIS_PASS")
-
local REDIS_FIELD = "resources"
-
local BASE_CONF_DIR = "/etc/api-gateway/managed_confs/"
local _M = {}
---- Add/update a resource to redis and create/update an nginx conf file given PUT JSON body
--- PUT http://0.0.0.0:9000/resources/<tenant>/<url-encoded-resource>
--- Example PUT JSON body:
+--------------------------
+---------- APIs ----------
+--------------------------
+
+--- Add an api to the Gateway
+-- PUT http://0.0.0.0:9000/APIs
+-- body:
-- {
--- "api": "12345"
--- "gatewayMethod": "GET",
--- "backendURL": "http://openwhisk.ng.bluemix.net/guest/action?blocking=true",
--- "backendMethod": "POST",
--- "policies": [],
--- "security": {
--- "type": "apikey"
--- }
--- }
-function _M.addResource()
+-- "name": *(String) name of API
+-- "basePath": *(String) base path for api
+-- "tenantId": *(String) tenant id
+-- "resources": *(String) resources to add
+-- }
+function _M.addAPI()
+ -- Open connection to redis or use one from connection pool
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+ -- Check for api id and use existingAPI if it already exists in redis
+ local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+ local apiId, existingAPI
+ local index = 1
+ for word in string.gmatch(uri, '([^/]+)') do
+ if index == 3 then
+ apiId = word
+ end
+ index = index + 1
+ end
+ if apiId ~= nil then
+ existingAPI = redis.getAPI(red, apiId)
+ if existingAPI == nil then
+ request.err(404, utils.concatStrings({"Unknown API id ", apiId}))
+ end
+ end
-- Read in the PUT JSON Body
ngx.req.read_body()
local args = ngx.req.get_body_data()
if not args then
- request.err(400, "Missing Request body")
+ request.err(400, "Missing request body")
end
-- Convert json into Lua table
local decoded = cjson.decode(args)
- -- Error handling for required fields in the request body
- local gatewayMethod = decoded.gatewayMethod
- if not gatewayMethod then
- request.err(400, "\"gatewayMethod\" missing from request body.")
+ -- Error checking
+ local fields = {"name", "basePath", "tenantId", "resources"}
+ for k, v in pairs(fields) do
+ local res, err = isValid(red, v, decoded[v])
+ if res == false then
+ request.err(err.statusCode, err.message)
+ end
end
- local backendUrl = decoded.backendURL
- if not backendUrl then
- request.err(400, "\"backendURL\" missing from request body.")
+ -- Format basePath
+ local basePath = decoded.basePath:sub(1,1) == '/' and decoded.basePath:sub(2) or decoded.basePath
+ -- Add resources to redis and create nginx conf files
+ for path, resource in pairs(decoded.resources) do
+ local gatewayPath = utils.concatStrings({basePath, ngx.escape_uri(path)})
+ addResource(red, resource, gatewayPath, decoded.tenantId)
end
- -- Use gatewayMethod by default or usebackendMethod if specified
- local backendMethod = decoded and decoded.backendMethod or gatewayMethod
- -- apiId, policies, security fields are optional
- local apiId = decoded.apiId
- -- TODO: Error handling needed for policies and security
- local policies = decoded.policies
- local security = decoded.security
- local requestURI = string.gsub(ngx.var.request_uri, "?.*", "")
- local list = parseRequestURI(requestURI)
- local tenant = list[2]
- local gatewayPath = list[3]
- local redisKey = utils.concatStrings({"resources", ":", tenant, ":", ngx.unescape_uri(gatewayPath)})
+ -- Return managedUrl object
+ local uuid = existingAPI ~= nil and existingAPI.id or utils.uuid()
+ local managedUrlObj = {
+ id = uuid,
+ name = decoded.name,
+ basePath = utils.concatStrings({"/", basePath}),
+ tenantId = decoded.tenantId,
+ resources = decoded.resources,
+ managedUrl = utils.concatStrings({"http://0.0.0.0:8080/api/", decoded.tenantId, "/", basePath})
+ }
+ managedUrlObj = cjson.encode(managedUrlObj):gsub("\\", "")
+ -- Add API object to redis
+ redis.addAPI(red, uuid, managedUrlObj)
+ redis.close(red)
+ -- Return managed url object
+ ngx.header.content_type = "application/json; charset=utf-8"
+ request.success(200, managedUrlObj)
+end
+
+--- Check JSON body fields for errors
+-- @param red Redis client instance
+-- @param field name of field
+-- @param object field object
+function isValid(red, field, object)
+ -- Check that field exists in body
+ if not object then
+ return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", field, "' in request body."}) }
+ end
+ -- Additional check f or tenantId
+ if field == "tenantId" then
+ local tenant = redis.getTenant(red, object)
+ if tenant == nil then
+ return false, { statusCode = 404, message = utils.concatStrings({"Unknown tenant id ", object }) }
+ end
+ end
+ -- Additional checks for resource object
+ if field == "resources" then
+ local resources = object
+ if next(object) == nil then
+ return false, { statusCode = 400, message = "Empty resources object." }
+ end
+ for path, resource in pairs(resources) do
+ -- Check that resource path begins with slash
+ if path:sub(1,1) ~= '/' then
+ return false, { statusCode = 400, message = "Resource path must begin with '/'." }
+ end
+ -- Check operations object
+ if not resource.operations or next(resource.operations) == nil then
+ return false, { statusCode = 400, message = "Missing or empty field 'operations' or in resource path object." }
+ end
+ for verb, verbObj in pairs(resource.operations) do
+ local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true}
+ if allowedVerbs[verb:upper()] == nil then
+ return false, { statusCode = 400, message = utils.concatStrings({"Resource verb '", verb, "' not supported."}) }
+ end
+ -- Check required fields
+ local requiredFields = {"backendMethod", "backendUrl"}
+ for k, v in pairs(requiredFields) do
+ if verbObj[v] == nil then
+ return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", v, "' for '", verb, "' operation."}) }
+ end
+ if v == "backendMethod" then
+ local backendMethod = verbObj[v]
+ if allowedVerbs[backendMethod:upper()] == nil then
+ return false, { statusCode = 400, message = utils.concatStrings({"backendMethod '", backendMethod, "' not supported."}) }
+ end
+ end
+ end
+ -- Check optional fields
+ local policies = verbObj.policies
+ if policies then
+ for k, v in pairs(policies) do
+ if v.type == nil then
+ return false, { statusCode = 400, message = "Missing field 'type' in policy object." }
+ end
+ end
+ end
+ local security = verbObj.security
+ if security and security.type == nil then
+ return false, { statusCode = 400, message = "Missing field 'type' in security object." }
+ end
+ end
+ end
+ end
+ -- All error checks passed
+ return true
+end
+
+--- Helper function for adding a resource to redis and creating an nginx conf file
+-- @param red
+-- @param resource
+-- @param gatewayPath
+-- @param tenantId
+function addResource(red, resource, gatewayPath, tenantId)
+ -- Create resource object and add to redis
+ local redisKey = utils.concatStrings({"resources", ":", tenantId, ":", ngx.unescape_uri(gatewayPath)})
+ local apiId
+ local operations
+ for k, v in pairs(resource) do
+ if k == 'apiId' then
+ apiId = v
+ elseif k == 'operations' then
+ operations = v
+ end
+ end
+ local resourceObj = redis.generateResourceObj(operations, apiId)
+ redis.createResource(red, redisKey, REDIS_FIELD, resourceObj)
+ filemgmt.createResourceConf(BASE_CONF_DIR, tenantId, gatewayPath, resourceObj)
+end
+
+--- Get one or all APIs from the gateway
+-- GET http://0.0.0.0:9000/APIs
+function _M.getAPIs()
+ local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+ local id
+ local index = 1
+ local tenantQuery = false
+ for word in string.gmatch(uri, '([^/]+)') do
+ if index == 3 then
+ id = word
+ elseif index == 4 then
+ if word == 'tenant' then
+ tenantQuery = true
+ else
+ request.err(400, "Invalid request")
+ end
+ end
+ index = index + 1
+ end
+ if id == nil then
+ getAllAPIs()
+ else
+ if tenantQuery == false then
+ getAPI(id)
+ else
+ getAPITenant(id)
+ end
+ end
+end
+
+--- Get all APIs in redis
+function getAllAPIs()
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+ local res = redis.getAllAPIs(red)
+ redis.close(red)
+ local apiList = {}
+ for k, v in pairs(res) do
+ if k%2 == 0 then
+ apiList[#apiList+1] = cjson.decode(v)
+ end
+ end
+ apiList = cjson.encode(apiList)
+ ngx.header.content_type = "application/json; charset=utf-8"
+ request.success(200, apiList)
+end
+
+--- Get API by its id
+-- @param id of API
+function getAPI(id)
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+ local api = redis.getAPI(red, id)
+ if api == nil then
+ request.err(404, utils.concatStrings({"Unknown api id ", id}))
+ end
+ redis.close(red)
+ ngx.header.content_type = "application/json; charset=utf-8"
+ request.success(200, cjson.encode(api))
+end
+
+--- Get belongsTo relation tenant
+-- @param id id of API
+function getAPITenant(id)
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+ local api = redis.getAPI(red, id)
+ if api == nil then
+ request.err(404, utils.concatStrings({"Unknown api id ", id}))
+ end
+ local tenantId = api.tenantId
+ local tenant = redis.getTenant(red, tenantId)
+ if tenant == nil then
+ request.err(404, utils.concatStrings({"Unknown tenant id ", tenantId}))
+ end
+ redis.close(red)
+ ngx.header.content_type = "application/json; charset=utf-8"
+ request.success(200, cjson.encode(tenant))
+end
+
+--- Delete API from gateway
+-- DELETE http://0.0.0.0:9000/APIs/<id>
+function _M.deleteAPI()
+ local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+ local index = 1
+ local id
+ for word in string.gmatch(uri, '([^/]+)') do
+ if index == 3 then
+ id = word
+ end
+ index = index + 1
+ end
+ if id == nil then
+ request.err(400, "No id specified.")
+ end
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+ local api = redis.getAPI(red, id)
+ if api == nil then
+ request.err(404, utils.concatStrings({"Unknown API id ", id}))
+ end
+ -- Delete all resources for the API
+ local basePath = api.basePath:sub(2)
+ for path, v in pairs(api.resources) do
+ local gatewayPath = utils.concatStrings({basePath, ngx.escape_uri(path)})
+ deleteResource(red, gatewayPath, api.tenantId)
+ end
+ redis.deleteAPI(red, id)
+ redis.close(red)
+ request.success(200, {})
+end
+
+--- Helper function for deleting resource in redis and appropriate conf files
+-- @param red redis instance
+-- @param gatewayPath path in gateway
+-- @param tenantId tenant id
+function deleteResource(red, gatewayPath, tenantId)
+ local redisKey = utils.concatStrings({"resources:", tenantId, ":", ngx.unescape_uri(gatewayPath)})
+ redis.deleteResource(red, redisKey, REDIS_FIELD)
+ filemgmt.deleteResourceConf(BASE_CONF_DIR, tenantId, gatewayPath)
+end
+
+-----------------------------
+---------- Tenants ----------
+-----------------------------
+
+--- Add a tenant to the Gateway
+-- PUT http://0.0.0.0:9000/Tenants
+-- body:
+-- {
+-- "namespace": *(String) tenant namespace
+-- "instance": *(String) tenant instance
+-- }
+function _M.addTenant()
-- Open connection to redis or use one from connection pool
local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
- local resourceObj = redis.generateResourceObj(red, redisKey, gatewayMethod, backendUrl, backendMethod, apiId, policies, security)
- redis.createResource(red, redisKey, REDIS_FIELD, resourceObj)
- filemgmt.createResourceConf(BASE_CONF_DIR, tenant, gatewayPath, resourceObj)
- -- Add current redis connection in the ngx_lua cosocket connection pool
- redis.close(red)
- -- Return managed url object
- local managedUrlObj = {
- managedUrl = utils.concatStrings({"http://0.0.0.0:8080/api/", tenant, "/", gatewayPath})
+ -- Check for tenant id and use existingTenant if it already exists in redis
+ local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+ local tenantId, existingTenant
+ local index = 1
+ for word in string.gmatch(uri, '([^/]+)') do
+ if index == 3 then
+ tenantId = word
+ end
+ index = index + 1
+ end
+ if tenantId ~= nil then
+ existingTenant = redis.getTenant(red, tenantId)
+ if existingTenant == nil then
+ request.err(400, utils.concatStrings({"Unknown tenant id ", tenantId}))
+ end
+ end
+ -- Read in the PUT JSON Body
+ ngx.req.read_body()
+ local args = ngx.req.get_body_data()
+ if not args then
+ request.err(400, "Missing request body")
+ end
+ -- Convert json into Lua table
+ local decoded = cjson.decode(args)
+ -- Error checking
+ local fields = {"namespace", "instance"}
+ for k, v in pairs(fields) do
+ if not decoded[v] then
+ request.err(400, utils.concatStrings({"Missing field '", v, "' in request body."}))
+ end
+ end
+ -- Return tenant object
+ local uuid = existingTenant ~= nil and existingTenant.id or utils.uuid()
+ local tenantObj = {
+ id = uuid,
+ namespace = decoded.namespace,
+ instance = decoded.instance
}
- managedUrlObj = cjson.encode(managedUrlObj):gsub("\\", "")
+ tenantObj = cjson.encode(tenantObj)
+ redis.addTenant(red, uuid, tenantObj)
+ redis.close(red)
ngx.header.content_type = "application/json; charset=utf-8"
- request.success(200, managedUrlObj)
+ request.success(200, tenantObj)
end
---- Get resource from redis
--- GET http://0.0.0.0:9000/resources/<tenant>/<url-encoded-resource>
-function _M.getResource()
- local requestURI = string.gsub(ngx.var.request_uri, "?.*", "")
- local list = parseRequestURI(requestURI)
- local tenant = list[2]
- local gatewayPath = list[3]
- local redisKey = utils.concatStrings({list[1], ":", tenant, ":", ngx.unescape_uri(gatewayPath)})
- -- Initialize and connect to redis
- local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
- local resourceObj = redis.getResource(red, redisKey, REDIS_FIELD)
- if resourceObj == nil then
- request.err(404, "Resource doesn't exist.")
+--- Get one or all tenants from the gateway
+-- GET http://0.0.0.0:9000/Tenants
+function _M.getTenants()
+ local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+ local id
+ local index = 1
+ local apiQuery = false
+ for word in string.gmatch(uri, '([^/]+)') do
+ if index == 3 then
+ id = word
+ elseif index == 4 then
+ if word:lower() == 'apis' then
+ apiQuery = true
+ else
+ request.err(400, "Invalid request")
+ end
+ end
+ index = index + 1
end
- -- Add current redis connection in the ngx_lua cosocket connection pool
- redis.close(red)
- -- Get available operations for the given resource
- resourceObj = cjson.decode(resourceObj)
- local operations = {}
- for k in pairs(resourceObj.operations) do
- operations[#operations+1] = k
+ if id == nil then
+ getAllTenants()
+ else
+ if apiQuery == false then
+ getTenant(id)
+ else
+ getTenantAPIs(id)
+ end
end
- -- Return managed url object
- local managedUrlObj = {
- managedUrl = utils.concatStrings({"http://0.0.0.0:8080/api/", tenant, "/", gatewayPath}),
- availableOperations = operations
- }
- managedUrlObj = cjson.encode(managedUrlObj):gsub("\\", "")
- ngx.header.content_type = "application/json; charset=utf-8"
- request.success(200, managedUrlObj)
end
---- Delete resource from redis
--- DELETE http://0.0.0.0:9000/resources/<tenant>/<url-encoded-resource>
-function _M.deleteResource()
- local requestURI = string.gsub(ngx.var.request_uri, "?.*", "")
- local list = parseRequestURI(requestURI)
- local tenant = list[2]
- local gatewayPath = list[3]
- local redisKey = utils.concatStrings({list[1], ":", tenant, ":", ngx.unescape_uri(gatewayPath)})
- -- Initialize and connect to redis
+--- Get all tenants in redis
+function getAllTenants()
local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
- -- Return if resource doesn't exist
- redis.deleteResource(red, redisKey, REDIS_FIELD)
- -- Delete conf file
- filemgmt.deleteResourceConf(BASE_CONF_DIR, tenant, gatewayPath)
- -- Add current redis connection in the ngx_lua cosocket connection pool
+ local res = redis.getAllTenants(red)
redis.close(red)
- request.success(200, "Resource deleted.")
+ local tenantList = {}
+ for k, v in pairs(res) do
+ if k%2 == 0 then
+ tenantList[#tenantList+1] = cjson.decode(v)
+ end
+ end
+ tenantList = cjson.encode(tenantList)
+ ngx.header.content_type = "application/json; charset=utf-8"
+ request.success(200, tenantList)
end
+--- Get tenant by its id
+-- @param id tenant id
+function getTenant(id)
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+ local tenant = redis.getTenant(red, id)
+ if tenant == nil then
+ request.err(404, utils.concatStrings({"Unknown tenant id ", id }))
+ end
+ redis.close(red)
+ ngx.header.content_type = "application/json; charset=utf-8"
+ request.success(200, cjson.encode(tenant))
+end
+
+--- Get APIs associated with tenant
+-- @param id tenant id
+function getTenantAPIs(id)
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+ local res = redis.getAllAPIs(red)
+ redis.close(red)
+ local apiList = {}
+ for k, v in pairs(res) do
+ if k%2 == 0 then
+ local decoded = cjson.decode(v)
+ if decoded.tenantId == id then
+ apiList[#apiList+1] = decoded
+ end
+ end
+ end
+ apiList = cjson.encode(apiList)
+ ngx.header.content_type = "application/json; charset=utf-8"
+ request.success(200, apiList)
+end
+
+--- Delete tenant from gateway
+-- DELETE http://0.0.0.0:9000/Tenants/<id>
+function _M.deleteTenant()
+ local uri = string.gsub(ngx.var.request_uri, "?.*", "")
+ local index = 1
+ local id
+ for word in string.gmatch(uri, '([^/]+)') do
+ if index == 3 then
+ id = word
+ end
+ index = index + 1
+ end
+ if id == nil then
+ request.err(400, "No id specified.")
+ end
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+ redis.deleteTenant(red, id)
+ redis.close(red)
+ request.success(200, {})
+end
+
+------------------------------
+----- Pub/Sub with Redis -----
+------------------------------
+
--- Subscribe to redis
-- GET http://0.0.0.0:9000/subscribe
function _M.subscribe()
@@ -169,6 +502,10 @@
request.success(200, "Unsubscribed to redis")
end
+---------------------------
+------ Subscriptions ------
+---------------------------
+
--- Add an apikey/subscription to redis
-- PUT http://0.0.0.0:9000/subscriptions
-- Body:
@@ -276,4 +613,4 @@
return list --prefix, tenant, gatewayPath, apiKey
end
-return _M
\ No newline at end of file
+return _M
diff --git a/api-gateway-config/scripts/lua/routing.lua b/api-gateway-config/scripts/lua/routing.lua
index 70f01ca..d1b45ec 100644
--- a/api-gateway-config/scripts/lua/routing.lua
+++ b/api-gateway-config/scripts/lua/routing.lua
@@ -48,7 +48,7 @@
if string.upper(verb) == ngx.req.get_method() then
-- Check if auth is required
local apiKey
- if (opFields.security and string.lower(opFields.security.type) == 'apikey') then
+ if (opFields.security and opFields.security.type ~= nil and string.lower(opFields.security.type) == 'apikey') then
apiKey = security.processAPIKey(opFields.security)
end
-- Parse backend url
diff --git a/api-gateway-config/tests/spec/test.lua b/api-gateway-config/tests/spec/test.lua
index 04da8e7..b3e88a0 100644
--- a/api-gateway-config/tests/spec/test.lua
+++ b/api-gateway-config/tests/spec/test.lua
@@ -121,24 +121,19 @@
it('should generate resource object to store in redis', function()
-- Resource object with no policies or security
- local key = 'resources:guest:hello'
- local gatewayMethod = 'GET'
- local backendUrl = 'https://httpbin.org/get'
- local backendMethod = gatewayMethod
local apiId = 12345
- local policies
- local security
+ local operations = {
+ GET = {
+ backendUrl = 'https://httpbin.org/get',
+ backendMethod = 'GET'
+ }
+ }
local resourceObj = {
- operations = {
- [gatewayMethod] = {
- backendUrl = backendUrl,
- backendMethod = backendMethod
- }
- },
- apiId = apiId
+ apiId = apiId,
+ operations = operations
}
local expected = resourceObj
- local generated = cjson.decode(redis.generateResourceObj(red, key, gatewayMethod, backendUrl, backendMethod, apiId, policies, security))
+ local generated = cjson.decode(redis.generateResourceObj(operations, apiId))
assert.are.same(expected, generated)
-- Resource object with policy added
@@ -153,10 +148,9 @@
}]
}]
]]
- policies = cjson.decode(policyList)
- resourceObj.operations[gatewayMethod].policies = policies
+ resourceObj.operations.GET.policies = cjson.decode(policyList)
expected = resourceObj
- generated = cjson.decode(redis.generateResourceObj(red, key, gatewayMethod, backendUrl, backendMethod, apiId, policies, security))
+ generated = cjson.decode(redis.generateResourceObj(operations, apiId))
assert.are.same(expected, generated)
-- Resource object with security added
@@ -167,24 +161,19 @@
"header":"myheader"
}
]]
- security = cjson.decode(securityObj)
- resourceObj.operations[gatewayMethod].security = security
+ resourceObj.operations.GET.security = cjson.decode(securityObj)
expected = resourceObj
- generated = cjson.decode(redis.generateResourceObj(red, key, gatewayMethod, backendUrl, backendMethod, apiId, policies, security))
+ generated = cjson.decode(redis.generateResourceObj(operations, apiId))
assert.are.same(expected, generated)
- -- Update already existing resource object
- local field = 'resources'
- redis.createResource(red, key, field, cjson.encode(generated))
- local newGatewayMethod = 'POST'
- resourceObj.operations[newGatewayMethod] = {
- backendUrl = backendUrl,
- backendMethod = backendMethod
+ -- Resource object with multiple operations
+ resourceObj.operations.PUT = {
+ backendUrl = 'https://httpbin.org/get',
+ backendMethod = 'PUT',
+ security = {}
}
- policies = nil
- security = nil
expected = resourceObj
- generated = cjson.decode(redis.generateResourceObj(red, key, newGatewayMethod, backendUrl, backendMethod, apiId, policies, security))
+ generated = cjson.decode(redis.generateResourceObj(operations, apiId))
assert.are.same(expected, generated)
end)