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)