Merge pull request #70 from openwhisk/develop

Merge develop to master, then flatten branch structure
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..5c783c9
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,17 @@
+language: python
+sudo: false
+
+env:
+  - LUA="lua=5.1"
+
+before_install:
+  - pip install hererocks
+  - hererocks lua_install -r^ --$LUA
+  - export PATH=$PATH:$PWD/lua_install/bin # Add directory with all installed binaries to PATH
+
+install:
+  - cd api-gateway-config/tests
+  - ./install-deps.sh
+
+script:
+  - busted --output=TAP --helper=set_paths spec/test.lua
\ No newline at end of file
diff --git a/api-gateway-config/conf.d/api_gateway_init.conf b/api-gateway-config/conf.d/api_gateway_init.conf
index a190517..c009531 100644
--- a/api-gateway-config/conf.d/api_gateway_init.conf
+++ b/api-gateway-config/conf.d/api_gateway_init.conf
@@ -53,15 +53,4 @@
 lua_shared_dict cachedkeys 50m; # caches api-keys
 lua_shared_dict cachedOauthTokens 50m; # caches OAuth tokens
 lua_shared_dict cachedUserProfiles 50m; # caches user profiles
-lua_shared_dict healthcheck_redis 1m; # used by lua health_check for redis cache
-
-# dictionaries used for storing the throttling / rate limiting rules
-lua_shared_dict tracking_rules_dict 5m;
-lua_shared_dict blocking_rules_dict 5m;
-lua_shared_dict delaying_rules_dict 5m;
-lua_shared_dict rewriting_rules_dict 5m;
-
-# metrics
-lua_shared_dict stats_counters 50m;
-lua_shared_dict stats_timers 50m;
-lua_shared_dict stats_all 50m;
\ No newline at end of file
+lua_shared_dict healthcheck_redis 1m; # used by lua health_check for redis cache
\ No newline at end of file
diff --git a/api-gateway-config/conf.d/includes/analytics_endpoints.conf b/api-gateway-config/conf.d/includes/analytics_endpoints.conf
index 3dbca5e..4e4b0c0 100644
--- a/api-gateway-config/conf.d/includes/analytics_endpoints.conf
+++ b/api-gateway-config/conf.d/includes/analytics_endpoints.conf
@@ -20,19 +20,6 @@
 # * DEALINGS IN THE SOFTWARE.
 # *
 # */
-#
-# This location exposes the metrics gathered by the legacy.metrics class
-#
-location /api-gateway-stats {
-    allow 127.0.0.1;
-    deny all;
-    content_by_lua '
-        local MetricsCls = require "metrics.MetricsBuffer"
-        local metrics = MetricsCls:new()
-        local json = assert( metrics:toJson(), "Could not read metrics")
-        ngx.say( json )
-    ';
-}
 
 location /nginx_status {
     stub_status on;
diff --git a/api-gateway-config/conf.d/managed_endpoints.conf b/api-gateway-config/conf.d/managed_endpoints.conf
index 37a5ed5..a7edb80 100644
--- a/api-gateway-config/conf.d/managed_endpoints.conf
+++ b/api-gateway-config/conf.d/managed_endpoints.conf
@@ -74,95 +74,4 @@
     location = /health {
         proxy_pass http://0.0.0.0:9000/v1/health-check;
     }
-}
-
-
-# example of hello-world service protected with API KEY
-#server {
-#    listen 80;
-#
-#    server_name ~hello-world.api.(?<domain>.+);
-#
-#    server_tokens off;
-#
-#    #turn off the uninitialized_variable_warn ,as it writes to error_log , hence io
-#    uninitialized_variable_warn off;
-#
-#    # block ips of embargoed countries
-#    if ( $blacklist ) {
-#        return 403;
-#    }
-#
-#    include /etc/api-gateway/conf.d/commons/common-headers.conf;
-#    include /etc/api-gateway/conf.d/includes/resolvers.conf;
-#    include /etc/api-gateway/conf.d/includes/default_validators.conf;
-#
-#    # Log locations with service name
-#    access_log /var/log/api-gateway/access.log platform;
-#    error_log /var/log/api-gateway/marathon_error.log debug;
-#
-#    # include environment variables
-#    include /etc/api-gateway/environment.conf.d/api-gateway-env-vars.server.conf;
-#
-#    error_page 500 501 502 503 504 /50x.html;
-#
-#    location /50x.html {
-#        more_set_headers 'Content-Type: application/json';
-#        more_set_headers 'X-Request-Id: $requestId';
-#        return 500 '{"code":$status, "message":"Oops. Something went wrong. Check your URI and try again."}\n';
-#    }
-#
-#    set $marathon_app_name hello-world;
-#
-#    location ~.*\.(png|jpg|jpeg|js)$ {
-#        proxy_pass http://$marathon_app_name$request_uri;
-#    }
-#
-#    location / {
-#        # ----------------------------------
-#        # add X-Request-Id header
-#        # ----------------------------------
-#        set $requestId $http_x_request_id;
-#        set_secure_random_alphanum $requestId_random 32;
-#        set_if_empty $requestId $requestId_random;
-#        # add_header X-Request-Id $requestId;
-#        proxy_set_header X-Request-Id  $requestId;
-#
-#        proxy_connect_timeout 10s;  # timeout for establishing a connection with a proxied server
-#
-#        proxy_read_timeout 10s;     # Defines a timeout for reading a response from the proxied server.
-#                                    # The timeout is set only between two successive read operations,
-#                                    # not for the transmission of the whole response.
-#
-#        proxy_send_timeout 10s;     # Sets a timeout for transmitting a request to the proxied server.
-#                                    # The timeout is set only between two successive write operations,
-#                                    # not for the transmission of the whole request.
-#        keepalive_timeout 10s;      # timeout during which a keep-alive client connection will stay open on the server side
-#        proxy_buffering off;        # enables or disables buffering of responses from the proxied server.
-#        proxy_http_version 1.1;     # Version 1.1 is recommended for use with keepalive connections.
-#        proxy_set_header Connection "";
-#
-#        # identify the service
-#        set $service_id $marathon_app_name;
-#
-#        # identify the api key
-#        #     either from the query params or from the "X-Api-Key" header
-#        set $api_key $arg_api_key;
-#        set_if_empty $api_key $http_x_api_key;
-#
-#        # add the api-key validator
-#        set $validate_api_key on;
-#
-#        #validate request
-#        access_by_lua "ngx.apiGateway.validation.validateRequest()";
-#
-#        proxy_pass http://$marathon_app_name$request_uri;
-#
-#        # capture usage data
-#        log_by_lua '
-#            if ( ngx.apiGateway.metrics ~= nil ) then
-#                ngx.apiGateway.metrics.captureUsageData()
-#            end
-#        ';
-#    }
-#}
+}
\ No newline at end of file
diff --git a/api-gateway-config/conf.d/management_apis.conf b/api-gateway-config/conf.d/management_apis.conf
index 3e31390..5a806d7 100644
--- a/api-gateway-config/conf.d/management_apis.conf
+++ b/api-gateway-config/conf.d/management_apis.conf
@@ -33,89 +33,33 @@
     access_log /var/log/api-gateway/access.log platform;
     error_log /var/log/api-gateway/management.log debug;
 
-    location /v1/apis {
+    location ~ /v1/apis/?(?<api_id>[^/]*)?/?(?<query>[^/]*)? {
         access_by_lua_block {
-            local mgmt = require("management")
-            local requestMethod = ngx.req.get_method()
-            if requestMethod == "GET" then
-                mgmt.getAPIs()
-            elseif requestMethod == "PUT" then
-                mgmt.addAPI()
-            elseif requestMethod == "POST" then
-                mgmt.addAPI()
-            elseif requestMethod == "DELETE" then
-                mgmt.deleteAPI()
-            else
-                ngx.status = 400
-                ngx.say("Invalid verb")
-            end
+            local apis = require("management/apis")
+            apis.requestHandler()
         }
     }
 
-    location /v1/tenants {
+    location ~ /v1/tenants/?(?<tenant_id>[^/]*)?/?(?<query>[^/]*)? {
         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")
-            end
+            local tenants = require("management/tenants")
+            tenants.requestHandler()
         }
     }
 
-    location /subscriptions {
+    location /v1/subscriptions {
          access_by_lua_block {
-             local mgmt = require("management")
-             local requestMethod = ngx.req.get_method()
-             if requestMethod == "PUT" then
-                 mgmt.addSubscription()
-             elseif requestMethod == "DELETE" then
-                 mgmt.deleteSubscription()
-             else
-                 ngx.status = 400
-                 ngx.say("Invalid verb")
-             end
+             local subscriptions = require("management/subscriptions")
+             subscriptions.requestHandler()
          }
     }
 
-    location /v1/sync {
-        access_by_lua_block {
-            local mgmt = require("management")
-            local requestMethod = ngx.req.get_method()
-            if requestMethod == "GET" then
-                mgmt.sync()
-            else
-                ngx.say("Invalid verb")
-            end
-        }
-    }
-
-    location /v1/subscribe {
-        access_by_lua_block {
-            local mgmt = require("management")
-            local requestMethod = ngx.req.get_method()
-            if requestMethod == "GET" then
-                mgmt.subscribe()
-            else
-                ngx.say("Invalid verb")
-            end
-        }
-    }
-
     location /v1/health-check {
         access_by_lua_block {
-            local mgmt = require("management")
             local requestMethod = ngx.req.get_method()
             if requestMethod == "GET" then
-                mgmt.healthCheck()
+                local request = require "lib/request"
+                request.success(200,  "Status: Gateway ready.")
             else
                 ngx.say("Invalid verb")
             end
diff --git a/api-gateway-config/scripts/lua/api_gateway_init.lua b/api-gateway-config/scripts/lua/api_gateway_init.lua
index a5f46b3..3241147 100644
--- a/api-gateway-config/scripts/lua/api_gateway_init.lua
+++ b/api-gateway-config/scripts/lua/api_gateway_init.lua
@@ -1,23 +1,3 @@
--- Copyright (c) 2015 Adobe Systems Incorporated. All rights reserved.
---
---   Permission is hereby granted, free of charge, to any person obtaining a
---   copy of this software and associated documentation files (the "Software"),
---   to deal in the Software without restriction, including without limitation
---   the rights to use, copy, modify, merge, publish, distribute, sublicense,
---   and/or sell copies of the Software, and to permit persons to whom the
---   Software is furnished to do so, subject to the following conditions:
---
---   The above copyright notice and this permission notice shall be included in
---   all copies or substantial portions of the Software.
---
---   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
---   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
---   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
---   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
---   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
---   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
---   DEALINGS IN THE SOFTWARE.
-
 -- An initialization script on a per worker basis.
 -- User: ddascal
 -- Date: 07/12/14
@@ -44,44 +24,7 @@
     return require(module)
 end
 
---- Initializes the `zmqLogger` used by `trackingRulesLogger.lua` from api-gateway-request-tracking module
--- @param parentObject
---
-local function initZMQLogger(parentObject)
-    ngx.log(ngx.DEBUG, "Initializing ZMQLogger on property [zmqLogger]")
-    -- when the ZmqModule is not present the script does not break
-    local ZmqLogger = loadrequire("api-gateway.zmq.ZeroMQLogger")
 
-    if (ZmqLogger == nil) then
-        return
-    end
-
-    local zmq_publish_address = "ipc:///tmp/nginx_queue_listen"
-    ngx.log(ngx.INFO, "Starting new ZmqLogger on pid [", tostring(ngx.worker.pid()), "] on address [", zmq_publish_address, "]")
-    local zmqLogger = ZmqLogger:new()
-    zmqLogger:connect(ZmqLogger.SOCKET_TYPE.ZMQ_PUB, zmq_publish_address)
-
-    parentObject.zmqLogger = zmqLogger
-end
-
-local function initValidationFactory(parentObject)
-    parentObject.validation = require "api-gateway.validation.factory"
-end
-
-local function initTrackingFactory(parentObject)
-    parentObject.tracking = require "api-gateway.tracking.factory"
-end
-
-local function initMetricsFactory(parentObject)
-    parentObject.metrics = require "metrics.factory"
-end
-
---initValidationFactory(_M)
---initZMQLogger(_M)
---initTrackingFactory(_M)
-initMetricsFactory(_M)
--- TODO: test health-check with the new version of Openresty
--- initRedisHealthCheck()
 
 ngx.apiGateway = _M
 
diff --git a/api-gateway-config/scripts/lua/lib/filemgmt.lua b/api-gateway-config/scripts/lua/lib/filemgmt.lua
deleted file mode 100644
index f1a6e1c..0000000
--- a/api-gateway-config/scripts/lua/lib/filemgmt.lua
+++ /dev/null
@@ -1,90 +0,0 @@
--- Copyright (c) 2016 IBM. All rights reserved.
---
---   Permission is hereby granted, free of charge, to any person obtaining a
---   copy of this software and associated documentation files (the "Software"),
---   to deal in the Software without restriction, including without limitation
---   the rights to use, copy, modify, merge, publish, distribute, sublicense,
---   and/or sell copies of the Software, and to permit persons to whom the
---   Software is furnished to do so, subject to the following conditions:
---
---   The above copyright notice and this permission notice shall be included in
---   all copies or substantial portions of the Software.
---
---   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
---   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
---   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
---   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
---   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
---   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
---   DEALINGS IN THE SOFTWARE.
-
---- @module filemgmt
--- Creates the Nginx Conf files
--- @author Alex Song (songs), David Green (greend)
-
-local utils = require "lib/utils"
-local cjson = require "cjson"
-local request = require "lib/request"
-
-local _M = {}
-
---- Create/overwrite Nginx Conf file for given resource
--- @param baseConfDir the base directory for storing conf files for managed resources
--- @param tenant the namespace for the resource
--- @param gatewayPath the gateway path of the resource
--- @param resourceObj object containing different operations/policies for the resource
--- @return fileLocation location of created/updated conf file
-function _M.createResourceConf(baseConfDir, tenant, gatewayPath, resourceObj)
-  local decoded = cjson.decode(resourceObj)
-  resourceObj = utils.serializeTable(decoded)
-  local prefix = utils.concatStrings({
-    "\tinclude /etc/api-gateway/conf.d/commons/common-headers.conf;\n",
-    "\tset $upstream https://172.17.0.1;\n",
-    "\tset $tenant ", tenant, ";\n",
-    "\tset $backendUrl '';\n",
-    "\tset $gatewayPath '", ngx.unescape_uri(gatewayPath), "';\n"
-  })
-  if decoded.apiId ~= nil then
-    prefix = utils.concatStrings({prefix, "\tset $apiId ", decoded.apiId, ";\n"})
-  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",
-    "\t\tlocal routing = require \"routing\"\n",
-    "\t\trouting.processCall(", resourceObj, ")\n",
-    "\t}\n",
-    "\tproxy_pass $upstream;\n"
-  })
-  -- Add to endpoint conf file
-  os.execute(utils.concatStrings({"mkdir -p ", baseConfDir, tenant}))
-  local fileLocation = utils.concatStrings({baseConfDir, tenant, "/", gatewayPath, ".conf"})
-  local file, err = io.open(fileLocation, "w")
-  if not file then
-    request.err(500, utils.concatStrings({"Error adding to endpoint conf file: ", err}))
-  end
-  local updatedPath = ngx.unescape_uri(gatewayPath):gsub("%{(%w*)%}", utils.convertTemplatedPathParam)
-  local location = utils.concatStrings({
-    "location ~ ^/api/", tenant, "/", updatedPath, "(\\b) {\n",
-    prefix,
-    outgoingResource,
-    "}\n"
-  })
-  file:write(location)
-  file:close()
-  return fileLocation
-end
-
---- Delete Ngx conf file for given resource
--- @param baseConfDir the base directory for storing conf files for managed resources
--- @param tenant the namespace for the resource
--- @param gatewayPath the gateway path of the resource
--- @return fileLocation location of deleted conf file
-function _M.deleteResourceConf(baseConfDir, tenant, gatewayPath)
-  local fileLocation = utils.concatStrings({baseConfDir, tenant, "/", gatewayPath, ".conf"})
-  os.execute(utils.concatStrings({"rm -f ", fileLocation}))
-  return fileLocation
-end
-
-return _M
diff --git a/api-gateway-config/scripts/lua/lib/redis.lua b/api-gateway-config/scripts/lua/lib/redis.lua
index 37491ca..8a4b47c 100644
--- a/api-gateway-config/scripts/lua/lib/redis.lua
+++ b/api-gateway-config/scripts/lua/lib/redis.lua
@@ -18,9 +18,8 @@
 --   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 --   DEALINGS IN THE SOFTWARE.
 
---- @module
---
--- @author Alex Song (songs)
+--- @module redis
+-- Module that the gateway uses to interact with redis
 
 local cjson = require "cjson"
 local utils = require "lib/utils"
diff --git a/api-gateway-config/scripts/lua/lib/utils.lua b/api-gateway-config/scripts/lua/lib/utils.lua
index d757c39..2a99642 100644
--- a/api-gateway-config/scripts/lua/lib/utils.lua
+++ b/api-gateway-config/scripts/lua/lib/utils.lua
@@ -19,8 +19,7 @@
 --   DEALINGS IN THE SOFTWARE.
 
 --- @module utils
--- Holds the common supporting functions in one file to be referenced else where
--- @author Alex Song (songs), Cody Walker (cmwalker), David Green (greend)
+-- Holds the common supporting functions in one file to be referenced elsewhere
 
 local _Utils = {}
 
@@ -28,7 +27,7 @@
 -- strings together with "..", which creates a new string every time
 -- @param list List of strings to concatenate
 -- @return concatenated string
-function concatStrings(list)
+function _Utils.concatStrings(list)
   local t = {}
   for k,v in ipairs(list) do
     t[#t+1] = tostring(v)
@@ -41,7 +40,7 @@
 -- Useful for saving a lua table to a file, as if not serialized it will save as "Table x35252"
 -- @param t The lua table
 -- @return String representing the serialized lua table
-function serializeTable(t)
+function _Utils.serializeTable(t)
   local first = true
   local tt = { '{' }
   for k, v in pairs(t) do
@@ -51,12 +50,12 @@
       first = false
     end
     if type(k) == 'string' then
-      tt[#tt+1] = concatStrings({tostring(k), ' = '})
+      tt[#tt+1] = _Utils.concatStrings({tostring(k), ' = '})
     end
     if type(v) == 'table' then
-      tt[#tt+1] = serializeTable(v)
+      tt[#tt+1] = _Utils.serializeTable(v)
     elseif type(v) == 'string' then
-      tt[#tt+1] = concatStrings({'"', tostring(v), '"'})
+      tt[#tt+1] = _Utils.concatStrings({'"', tostring(v), '"'})
     else
       tt[#tt+1] = tostring(v)
     end
@@ -69,13 +68,13 @@
 -- at time of being called by the user
 -- @param m where m is the string "{pathParam}"
 -- @return concatenated string of (?<path_pathParam>(\\w+))
-function convertTemplatedPathParam(m)
+function _Utils.convertTemplatedPathParam(m)
   local x = m:gsub("{", ""):gsub("}", "")
   return concatStrings({"(?<path_" , x , ">([a-zA-Z0-9\\-\\s\\_\\%]*))"})
 end
 
 --- Generate random uuid
-function uuid()
+function _Utils.uuid()
   local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
   math.randomseed(os.clock())
   return string.gsub(template, '[xy]', function (c)
@@ -87,7 +86,7 @@
 --- Check if element exists in table as value
 -- @param table table to check
 -- @param element element to check in table
-function tableContains(table, element)
+function _Utils.tableContains(table, element)
   for i, value in pairs(table) do
     if value == element then
       return true
@@ -96,11 +95,17 @@
   return false
 end
 
-_Utils.concatStrings = concatStrings
-_Utils.serializeTable = serializeTable
-_Utils.convertTemplatedPathParam = convertTemplatedPathParam
-_Utils.uuid = uuid
-_Utils.tableContains = tableContains
+--- Check if all required fields exist in the table
+-- @param table table to check
+-- @param requiredFields list of required fields
+function _Utils.tableContainsAll(table, requiredFields)
+  for i, field in ipairs(requiredFields) do
+    if not table[field] then
+      return false, { statusCode = 400, message = _Utils.concatStrings({"\"", field, "\" missing from request body."}) }
+    end
+  end
+  return true
+end
 
 return _Utils
 
diff --git a/api-gateway-config/scripts/lua/management.lua b/api-gateway-config/scripts/lua/management.lua
deleted file mode 100644
index ad87c0b..0000000
--- a/api-gateway-config/scripts/lua/management.lua
+++ /dev/null
@@ -1,679 +0,0 @@
--- Copyright (c) 2016 IBM. All rights reserved.
---
---   Permission is hereby granted, free of charge, to any person obtaining a
---   copy of this software and associated documentation files (the "Software"),
---   to deal in the Software without restriction, including without limitation
---   the rights to use, copy, modify, merge, publish, distribute, sublicense,
---   and/or sell copies of the Software, and to permit persons to whom the
---   Software is furnished to do so, subject to the following conditions:
---
---   The above copyright notice and this permission notice shall be included in
---   all copies or substantial portions of the Software.
---
---   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
---   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
---   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
---   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
---   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
---   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
---   DEALINGS IN THE SOFTWARE.
-
---- @module management
--- Defines and exposes a lightweight API management to create and remove resources in the running API Gateway
--- @author Alex Song (songs)
-
-local cjson = require "cjson"
-local redis = require "lib/redis"
-local utils = require "lib/utils"
-local logger = require "lib/logger"
-local request = require "lib/request"
-local MANAGEDURL_HOST = os.getenv("PUBLIC_MANAGEDURL_HOST")
-MANAGEDURL_HOST = (MANAGEDURL_HOST ~= nil and MANAGEDURL_HOST ~= '') and MANAGEDURL_HOST or "0.0.0.0"
-local MANAGEDURL_PORT = os.getenv("PUBLIC_MANAGEDURL_PORT")
-MANAGEDURL_PORT = (MANAGEDURL_PORT ~= nil and MANAGEDURL_PORT ~= '') and MANAGEDURL_PORT or "8080"
-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 = {}
-
---------------------------
----------- APIs ----------
---------------------------
-
---- Add an api to the Gateway
--- PUT /v1/apis
--- body:
--- {
---    "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, 10000)
-  -- Check for api id from uri and use existingAPI if it already exists in redis
-  local uri = string.gsub(ngx.var.request_uri, "?.*", "")
-  local existingAPI = checkURIForExisting(red, uri, "api")
-  -- 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)
-  -- Check for api id in JSON body
-  if existingAPI == nil and decoded.id ~= nil then
-    existingAPI = redis.getAPI(red, decoded.id)
-    if existingAPI == nil then
-      request.err(404, utils.concatStrings({"Unknown API id ", decoded.id}))
-    end
-  end
-  -- 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
-  -- Format basePath
-  local basePath = decoded.basePath:sub(1,1) == '/' and decoded.basePath:sub(2) or decoded.basePath
-  basePath = basePath:sub(-1) == '/' and basePath:sub(1, -2) or basePath
-  -- Create managedUrl object
-  local uuid = existingAPI ~= nil and existingAPI.id or utils.uuid()
-  local managedUrl = utils.concatStrings({"http://", MANAGEDURL_HOST, ":", MANAGEDURL_PORT, "/api/", decoded.tenantId})
-  if basePath:sub(1,1) ~= '' then
-    managedUrl = utils.concatStrings({managedUrl, "/", basePath})
-  end
-  local managedUrlObj = {
-    id = uuid,
-    name = decoded.name,
-    basePath = utils.concatStrings({"/", basePath}),
-    tenantId = decoded.tenantId,
-    resources = decoded.resources,
-    managedUrl = managedUrl
-  }
-  -- Add API object to redis
-  managedUrlObj = redis.addAPI(red, uuid, managedUrlObj, existingAPI)
-  -- Add resources to redis
-  for path, resource in pairs(decoded.resources) do
-    local gatewayPath = utils.concatStrings({basePath, path})
-    gatewayPath = (gatewayPath:sub(1,1) == '/') and gatewayPath:sub(2) or gatewayPath
-    addResource(red, resource, gatewayPath, decoded.tenantId)
-  end
-  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 for basePath
-  if field == "basePath" then
-    local basePath = object
-    if string.match(basePath, "'") then
-      return false, { statusCode = 400, message = "basePath contains illegal character \"'\"." }
-    end
-  end
-  -- Additional check for 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
-  if field == "resources" then
-    local res, err = checkResources(object)
-    if res ~= nil and res == false then
-      return res, err
-    end
-  end
-  -- All error checks passed
-  return true
-end
-
---- Error checking for resources
--- @param resources resources object
-function checkResources(resources)
-  if next(resources) == nil then
-    return false, { statusCode = 400, message = "Empty resources object." }
-  end
-  for path, resource in pairs(resources) do
-    -- Check resource path for illegal characters
-    if string.match(path, "'") then
-      return false, { statusCode = 400, message = "resource path contains illegal character \"'\"." }
-    end
-    -- 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
-    local res, err = checkOperations(resource.operations)
-    if res ~= nil and res == false then
-      return res, err
-    end
-  end
-end
-
---- Error checking for operations
--- @param operations operations object
-function checkOperations(operations)
-  if not operations or next(operations) == nil then
-    return false, { statusCode = 400, message = "Missing or empty field 'operations' or in resource path object." }
-  end
-  local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true}
-  for verb, verbObj in pairs(operations) do
-    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 res, err = checkOptionalPolicies(verbObj.policies, verbObj.security)
-    if res ~= nil and res == false then
-      return res, err
-    end
-  end
-end
-
---- Error checking for policies and security
--- @param policies policies object
--- @param security security object
-function checkOptionalPolicies(policies, security)
-  if policies then
-    for k, v in pairs(policies) do
-      local validTypes = {reqMapping = true, rateLimit = true}
-      if (v.type == nil or v.value == nil) then
-        return false, { statusCode = 400, message = "Missing field in policy object. Need \"type\" and \"scope\"." }
-      elseif validTypes[v.type] == nil then
-        return false, { statusCode = 400, message = "Invalid type in policy object. Valid: \"reqMapping\", \"rateLimit\"" }
-      end
-    end
-  end
-  if security then
-    local validScopes = {tenant=true, api=true, resource=true}
-    if (security.type == nil or security.scope == nil) then
-      return false, { statusCode = 400, message = "Missing field in security object. Need \"type\" and \"scope\"." }
-    elseif validScopes[security.scope] == nil then
-      return false, { statusCode = 400, message = "Invalid scope in security object. Valid: \"tenant\", \"api\", \"resource\"." }
-    end
-  end
-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, ":", 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)
-end
-
---- Get one or all APIs from the gateway
--- GET /v1/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, 10000)
-  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, 10000)
-  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, 10000)
-  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 /v1/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, 10000)
-  local api = redis.getAPI(red, id)
-  if api == nil then
-    request.err(404, utils.concatStrings({"Unknown API id ", id}))
-  end
-  -- Delete API
-  redis.deleteAPI(red, id)
-  -- 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, path})
-    gatewayPath = (gatewayPath:sub(1,1) == '/') and gatewayPath:sub(2) or gatewayPath
-    deleteResource(red, gatewayPath, api.tenantId)
-  end
-  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, ":", gatewayPath})
-  redis.deleteResource(red, redisKey, REDIS_FIELD)
-end
-
------------------------------
----------- Tenants ----------
------------------------------
-
---- Add a tenant to the Gateway
--- PUT /v1/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, 10000)
-  -- Check for tenant id and use existingTenant if it already exists in redis
-  local uri = string.gsub(ngx.var.request_uri, "?.*", "")
-  local existingTenant = checkURIForExisting(red, uri, "tenant")
-  -- 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)
-  -- Check for tenant id in JSON body
-  if existingTenant == nil and decoded.id ~= nil then
-    existingTenant = redis.getTenant(red, decoded.id)
-    if existingTenant == nil then
-      request.err(404, utils.concatStrings({"Unknown Tenant id ", decoded.id}))
-    end
-  end
-  -- 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
-  }
-  tenantObj = redis.addTenant(red, uuid, tenantObj)
-  redis.close(red)
-  ngx.header.content_type = "application/json; charset=utf-8"
-  request.success(200, tenantObj)
-end
-
---- Get one or all tenants from the gateway
--- GET /v1/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
-  if id == nil then
-    getAllTenants()
-  else
-    if apiQuery == false then
-      getTenant(id)
-    else
-      getTenantAPIs(id)
-    end
-  end
-end
-
---- Get all tenants in redis
-function getAllTenants()
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
-  local res = redis.getAllTenants(red)
-  redis.close(red)
-  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, 10000)
-  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, 10000)
-  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 /v1/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 /v1/subscribe
-function _M.subscribe()
-  redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS)
-  logger.info(utils.concatStrings({"Connected to redis at ", REDIS_HOST, ":", REDIS_PORT}))
-  while true do end
-  ngx.exit(200)
-end
-
-----------------------------
-------- Health Check -------
-----------------------------
-
---- Check health of gateway
-function _M.healthCheck()
-  redis.healthCheck()
-end
-
----------------------------
------- Subscriptions ------
----------------------------
-
---- Add an apikey/subscription to redis
--- PUT /subscriptions
--- Body:
--- {
---    key: *(String) key for tenant/api/resource
---    scope: *(String) tenant or api or resource
---    tenantId: *(String) tenant id
---    resource: (String) url-encoded resource path
---    apiId: (String) api id
--- }
-function _M.addSubscription()
-  -- Validate body and create redisKey
-  local redisKey = validateSubscriptionBody()
-  -- Open connection to redis or use one from connection pool
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
-  redis.createSubscription(red, redisKey)
-  -- Add current redis connection in the ngx_lua cosocket connection pool
-  redis.close(red)
-  request.success(200, "Subscription created.")
-end
-
---- Delete apikey/subscription from redis
--- DELETE /subscriptions
--- Body:
--- {
---    key: *(String) key for tenant/api/resource
---    scope: *(String) tenant or api or resource
---    tenantId: *(String) tenant id
---    resource: (String) url-encoded resource path
---    apiId: (String) api id
--- }
-function _M.deleteSubscription()
-  -- Validate body and create redisKey
-  local redisKey = validateSubscriptionBody()
-  -- Initialize and connect to redis
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
-  -- Return if subscription doesn't exist
-  redis.deleteSubscription(red, redisKey)
-  -- Add current redis connection in the ngx_lua cosocket connection pool
-  redis.close(red)
-  request.success(200, "Subscription deleted.")
-end
-
---- Check the request JSON body for correct fields
--- @return redisKey subscription key for redis
-function validateSubscriptionBody()
-  -- 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)
-  -- Check required fields
-  local requiredFieldList = {"key", "scope", "tenantId"}
-  for i, field in ipairs(requiredFieldList) do
-    if not decoded[field] then
-      request.err(400, utils.concatStrings({"\"", field, "\" missing from request body."}))
-    end
-  end
-  -- Check if we're using tenant or resource or api
-  local resource = decoded.resource
-  local apiId = decoded.apiId
-  local redisKey
-  local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenantId})
-  if decoded.scope == "tenant" then
-    redisKey = prefix
-  elseif decoded.scope == "resource" then
-    if resource ~= nil then
-      redisKey = utils.concatStrings({prefix, ":resource:", resource})
-    else
-      request.err(400, "\"resource\" missing from request body.")
-    end
-  elseif decoded.scope == "api" then
-    if apiId ~= nil then
-      redisKey = utils.concatStrings({prefix, ":api:", apiId})
-    else
-      request.err(400, "\"apiId\" missing from request body.")
-    end
-  else 
-    request.err(400, "Invalid scope")
-  end
-  redisKey = utils.concatStrings({redisKey, ":key:", decoded.key})
-  return redisKey
-end
-
-
---- Check for api id from uri and use existingAPI if it already exists in redis
--- @param red Redis client instance
--- @param uri Uri of request. Eg. /v1/apis/{id}
--- @param type type to look for in redis. "api" or "tenant"
-function checkURIForExisting(red, uri, type)
-  local id, existing
-  local index = 1
-  -- Check if id is in the uri
-  for word in string.gmatch(uri, '([^/]+)') do
-    if index == 3 then
-      id = word
-    end
-    index = index + 1
-  end
-  -- Get object from redis
-  if id ~= nil then
-    if type == "api" then
-      existing = redis.getAPI(red, id)
-      if existing == nil then
-        request.err(404, utils.concatStrings({"Unknown API id ", id}))
-      end
-    elseif type == "tenant" then
-      existing = redis.getTenant(red, id)
-      if existing == nil then
-        request.err(404, utils.concatStrings({"Unknown Tenant id ", id}))
-      end
-    end
-  end
-  return existing
-end
-
---- Parse the request uri to get the redisKey, tenant, and gatewayPath
--- @param requestURI String containing the uri in the form of "/resources/<tenant>/<path>"
--- @return list containing redisKey, tenant, gatewayPath
-function parseRequestURI(requestURI)
-  local list = {}
-  for i in string.gmatch(requestURI, '([^/]+)') do
-    list[#list + 1] = i
-  end
-  if not list[1] or not list[2] then
-    request.err(400, "Request path should be \"/resources/<tenant>/<url-encoded-resource>\"")
-  end
-
-  return list  --prefix, tenant, gatewayPath, apiKey
-end
-
-return _M
diff --git a/api-gateway-config/scripts/lua/management/apis.lua b/api-gateway-config/scripts/lua/management/apis.lua
new file mode 100644
index 0000000..bbc12af
--- /dev/null
+++ b/api-gateway-config/scripts/lua/management/apis.lua
@@ -0,0 +1,377 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module apis
+-- Management interface for apis for the gateway
+
+local cjson = require "cjson"
+local redis = require "lib/redis"
+local utils = require "lib/utils"
+local request = require "lib/request"
+local resources = require "management/resources"
+
+local MANAGEDURL_HOST = os.getenv("PUBLIC_MANAGEDURL_HOST")
+MANAGEDURL_HOST = (MANAGEDURL_HOST ~= nil and MANAGEDURL_HOST ~= '') and MANAGEDURL_HOST or "0.0.0.0"
+local MANAGEDURL_PORT = os.getenv("PUBLIC_MANAGEDURL_PORT")
+MANAGEDURL_PORT = (MANAGEDURL_PORT ~= nil and MANAGEDURL_PORT ~= '') and MANAGEDURL_PORT or "8080"
+local REDIS_HOST = os.getenv("REDIS_HOST")
+local REDIS_PORT = os.getenv("REDIS_PORT")
+local REDIS_PASS = os.getenv("REDIS_PASS")
+
+local _M = {}
+
+--- Request handler for routing API calls appropriately
+function _M.requestHandler()
+  local requestMethod = ngx.req.get_method()
+  if requestMethod == "GET" then
+    getAPIs()
+  elseif requestMethod == "PUT" then
+    addAPI()
+  elseif requestMethod == "POST" then
+    addAPI()
+  elseif requestMethod == "DELETE" then
+    deleteAPI()
+  else
+    ngx.status = 400
+    ngx.say("Invalid verb")
+  end
+end
+
+--- Add an api to the Gateway
+-- PUT /v1/apis
+-- body:
+-- {
+--    "name": *(String) name of API
+--    "basePath": *(String) base path for api
+--    "tenantId": *(String) tenant id
+--    "resources": *(String) resources to add
+-- }
+function addAPI()
+  -- Open connection to redis or use one from connection pool
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  -- Check for api id from uri and use existingAPI if it already exists in redis
+  local existingAPI = checkForExistingAPI(red)
+  -- 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)
+  -- Check for api id in JSON body
+  if existingAPI == nil and decoded.id ~= nil then
+    existingAPI = redis.getAPI(red, decoded.id)
+    if existingAPI == nil then
+      request.err(404, utils.concatStrings({"Unknown API id ", decoded.id}))
+    end
+  end
+  -- 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
+  -- Format basePath
+  local basePath = decoded.basePath:sub(1,1) == '/' and decoded.basePath:sub(2) or decoded.basePath
+  basePath = basePath:sub(-1) == '/' and basePath:sub(1, -2) or basePath
+  -- Create managedUrl object
+  local uuid = existingAPI ~= nil and existingAPI.id or utils.uuid()
+  local managedUrl = utils.concatStrings({"http://", MANAGEDURL_HOST, ":", MANAGEDURL_PORT, "/api/", decoded.tenantId})
+  if basePath:sub(1,1) ~= '' then
+    managedUrl = utils.concatStrings({managedUrl, "/", basePath})
+  end
+  local managedUrlObj = {
+    id = uuid,
+    name = decoded.name,
+    basePath = utils.concatStrings({"/", basePath}),
+    tenantId = decoded.tenantId,
+    resources = decoded.resources,
+    managedUrl = managedUrl
+  }
+  -- Add API object to redis
+  managedUrlObj = redis.addAPI(red, uuid, managedUrlObj, existingAPI)
+  -- Add resources to redis
+  for path, resource in pairs(decoded.resources) do
+    local gatewayPath = utils.concatStrings({basePath, path})
+    gatewayPath = (gatewayPath:sub(1,1) == '/') and gatewayPath:sub(2) or gatewayPath
+    resources.addResource(red, resource, gatewayPath, decoded.tenantId)
+  end
+  redis.close(red)
+  -- Return managed url object
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, managedUrlObj)
+end
+
+--- Check for api id from uri and use existing API if it already exists in redis
+-- @param red Redis client instance
+function checkForExistingAPI(red)
+  local id = ngx.var.api_id
+  local existing
+  -- Get object from redis
+  if id ~= nil and id ~= '' then
+    existing = redis.getAPI(red, id)
+    if existing == nil then
+      request.err(404, utils.concatStrings({"Unknown API id ", id}))
+    end
+  end
+  return existing
+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 for basePath
+  if field == "basePath" then
+    local basePath = object
+    if string.match(basePath, "'") then
+      return false, { statusCode = 400, message = "basePath contains illegal character \"'\"." }
+    end
+  end
+  -- Additional check for 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
+  if field == "resources" then
+    local res, err = checkResources(object)
+    if res ~= nil and res == false then
+      return res, err
+    end
+  end
+  -- All error checks passed
+  return true
+end
+
+--- Error checking for resources
+-- @param resources resources object
+function checkResources(resources)
+  if next(resources) == nil then
+    return false, { statusCode = 400, message = "Empty resources object." }
+  end
+  for path, resource in pairs(resources) do
+    -- Check resource path for illegal characters
+    if string.match(path, "'") then
+      return false, { statusCode = 400, message = "resource path contains illegal character \"'\"." }
+    end
+    -- 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
+    local res, err = checkOperations(resource.operations)
+    if res ~= nil and res == false then
+      return res, err
+    end
+  end
+end
+
+--- Error checking for operations
+-- @param operations operations object
+function checkOperations(operations)
+  if not operations or next(operations) == nil then
+    return false, { statusCode = 400, message = "Missing or empty field 'operations' or in resource path object." }
+  end
+  local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true}
+  for verb, verbObj in pairs(operations) do
+    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 res, err = checkOptionalPolicies(verbObj.policies, verbObj.security)
+    if res ~= nil and res == false then
+      return res, err
+    end
+  end
+end
+
+--- Error checking for policies and security
+-- @param policies policies object
+-- @param security security object
+function checkOptionalPolicies(policies, security)
+  if policies then
+    for k, v in pairs(policies) do
+      local validTypes = {reqMapping = true, rateLimit = true}
+      if (v.type == nil or v.value == nil) then
+        return false, { statusCode = 400, message = "Missing field in policy object. Need \"type\" and \"scope\"." }
+      elseif validTypes[v.type] == nil then
+        return false, { statusCode = 400, message = "Invalid type in policy object. Valid: \"reqMapping\", \"rateLimit\"" }
+      end
+    end
+  end
+  if security then
+    local validScopes = {tenant=true, api=true, resource=true}
+    if (security.type == nil or security.scope == nil) then
+      return false, { statusCode = 400, message = "Missing field in security object. Need \"type\" and \"scope\"." }
+    elseif validScopes[security.scope] == nil then
+      return false, { statusCode = 400, message = "Invalid scope in security object. Valid: \"tenant\", \"api\", \"resource\"." }
+    end
+  end
+end
+
+--- Get one or all APIs from the gateway
+-- GET /v1/apis
+function getAPIs()
+  local queryParams = ngx.req.get_uri_args()
+  local id = ngx.var.api_id
+  if id == nil or id == '' then
+    getAllAPIs(queryParams)
+  else
+    local query = ngx.var.query
+    if (query ~= nil and query ~= '') then
+      if query ~= "tenant" then
+        request.err(400, "Invalid request")
+      else
+        getAPITenant(id)
+      end
+    else
+      getAPI(id)
+    end
+  end
+end
+
+--- Get all APIs in redis
+-- @param queryParams object containing optional query parameters
+function getAllAPIs(queryParams)
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  local apis = redis.getAllAPIs(red)
+  redis.close(red)
+  local apiList
+  if next(queryParams) ~= nil then
+    apiList = filterAPIs(apis, queryParams);
+  end
+  if apiList == nil then
+    apiList = {}
+    for k, v in pairs(apis) do
+      if k%2 == 0 then
+        apiList[#apiList+1] = cjson.decode(v)
+      end
+    end
+    apiList = cjson.encode(apiList)
+  end
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, apiList)
+end
+
+--- Filter APIs based on query parameters
+-- @param apis list of apis
+-- @param queryParams query parameters to filter tenants
+function filterAPIs(apis, queryParams)
+  local basePath = queryParams['filter[where][basePath]']
+  local name = queryParams['filter[where][name]']
+  -- missing or invalid query parameters
+  if (basePath == nil and name == nil) then
+    return nil
+  end
+  -- filter tenants
+  local apiList = {}
+  for k, v in pairs(apis) do
+    if k%2 == 0 then
+      local api = cjson.decode(v)
+      if (basePath ~= nil and name == nil and api.basePath == basePath) or
+         (name ~= nil and basePath == nil and api.name == name) or
+         (basePath ~= nil and name ~= nil and api.basePath == basePath and api.name == name) then
+       apiList[#apiList+1] = api
+      end
+    end
+  end
+  return cjson.encode(apiList)
+end
+
+--- Get API by its id
+-- @param id of API
+function getAPI(id)
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  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, 10000)
+  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 /v1/apis/<id>
+function deleteAPI()
+  local id = ngx.var.api_id
+  if id == nil or id == '' then
+    request.err(400, "No id specified.")
+  end
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  local api = redis.getAPI(red, id)
+  if api == nil then
+    request.err(404, utils.concatStrings({"Unknown API id ", id}))
+  end
+  -- Delete API
+  redis.deleteAPI(red, id)
+  -- 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, path})
+    gatewayPath = (gatewayPath:sub(1,1) == '/') and gatewayPath:sub(2) or gatewayPath
+    resources.deleteResource(red, gatewayPath, api.tenantId)
+  end
+  redis.close(red)
+  request.success(200, {})
+end
+
+return _M;
diff --git a/api-gateway-config/scripts/lua/management/resources.lua b/api-gateway-config/scripts/lua/management/resources.lua
new file mode 100644
index 0000000..9df4e41
--- /dev/null
+++ b/api-gateway-config/scripts/lua/management/resources.lua
@@ -0,0 +1,54 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module resources
+-- Management interface for resources for the gateway
+
+local redis = require "lib/redis"
+local utils = require "lib/utils"
+
+local REDIS_FIELD = "resources"
+local _M = {}
+
+--- Helper function for adding a resource to redis and creating an nginx conf file
+-- @param red
+-- @param resource
+-- @param gatewayPath
+-- @param tenantId
+function _M.addResource(red, resource, gatewayPath, tenantId)
+  -- Create resource object and add to redis
+  local redisKey = utils.concatStrings({"resources", ":", tenantId, ":", gatewayPath})
+  local apiId
+  local operations = resource.operations
+  local apiId = resource.apiId
+  local resourceObj = redis.generateResourceObj(operations, apiId)
+  redis.createResource(red, redisKey, REDIS_FIELD, resourceObj)
+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 _M.deleteResource(red, gatewayPath, tenantId)
+  local redisKey = utils.concatStrings({"resources:", tenantId, ":", gatewayPath})
+  redis.deleteResource(red, redisKey, REDIS_FIELD)
+end
+
+return _M
diff --git a/api-gateway-config/scripts/lua/management/subscriptions.lua b/api-gateway-config/scripts/lua/management/subscriptions.lua
new file mode 100644
index 0000000..6193272
--- /dev/null
+++ b/api-gateway-config/scripts/lua/management/subscriptions.lua
@@ -0,0 +1,133 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module subscriptions
+-- Management interface for subscriptions for the gateway
+
+local cjson = require "cjson"
+local redis = require "lib/redis"
+local utils = require "lib/utils"
+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 _M = {}
+
+--- Request handler for routing API calls appropriately
+function _M.requestHandler()
+  local requestMethod = ngx.req.get_method()
+  if requestMethod == "PUT" then
+    addSubscription()
+  elseif requestMethod == "DELETE" then
+    deleteSubscription()
+  else
+    ngx.status = 400
+    ngx.say("Invalid verb")
+  end
+end
+
+--- Add an apikey/subscription to redis
+-- PUT /v1/subscriptions
+-- Body:
+-- {
+--    key: *(String) key for tenant/api/resource
+--    scope: *(String) tenant or api or resource
+--    tenantId: *(String) tenant id
+--    resource: (String) url-encoded resource path
+--    apiId: (String) api id
+-- }
+function addSubscription()
+  -- Validate body and create redisKey
+  local redisKey = validateSubscriptionBody()
+  -- Open connection to redis or use one from connection pool
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  redis.createSubscription(red, redisKey)
+  -- Add current redis connection in the ngx_lua cosocket connection pool
+  redis.close(red)
+  request.success(200, "Subscription created.")
+end
+
+--- Delete apikey/subscription from redis
+-- DELETE /v1/subscriptions
+-- Body:
+-- {
+--    key: *(String) key for tenant/api/resource
+--    scope: *(String) tenant or api or resource
+--    tenantId: *(String) tenant id
+--    resource: (String) url-encoded resource path
+--    apiId: (String) api id
+-- }
+function deleteSubscription()
+  -- Validate body and create redisKey
+  local redisKey = validateSubscriptionBody()
+  -- Initialize and connect to redis
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  -- Return if subscription doesn't exist
+  redis.deleteSubscription(red, redisKey)
+  -- Add current redis connection in the ngx_lua cosocket connection pool
+  redis.close(red)
+  request.success(200, "Subscription deleted.")
+end
+
+--- Check the request JSON body for correct fields
+-- @return redisKey subscription key for redis
+function validateSubscriptionBody()
+  -- 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)
+  -- Check required fields
+  local res, err = utils.tableContainsAll(decoded, {"key", "scope", "tenantId"})
+  if res == false then
+    request.err(err.statusCode, err.message)
+  end
+  -- Check if we're using tenant or resource or api
+  local resource = decoded.resource
+  local apiId = decoded.apiId
+  local redisKey
+  local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenantId})
+  if decoded.scope == "tenant" then
+    redisKey = prefix
+  elseif decoded.scope == "resource" then
+    if resource ~= nil then
+      redisKey = utils.concatStrings({prefix, ":resource:", resource})
+    else
+      request.err(400, "\"resource\" missing from request body.")
+    end
+  elseif decoded.scope == "api" then
+    if apiId ~= nil then
+      redisKey = utils.concatStrings({prefix, ":api:", apiId})
+    else
+      request.err(400, "\"apiId\" missing from request body.")
+    end
+  else
+    request.err(400, "Invalid scope")
+  end
+  redisKey = utils.concatStrings({redisKey, ":key:", decoded.key})
+  return redisKey
+end
+
+return _M
\ No newline at end of file
diff --git a/api-gateway-config/scripts/lua/management/tenants.lua b/api-gateway-config/scripts/lua/management/tenants.lua
new file mode 100644
index 0000000..a173e12
--- /dev/null
+++ b/api-gateway-config/scripts/lua/management/tenants.lua
@@ -0,0 +1,261 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module tenants
+-- Management interface for tenants for the gateway
+
+local cjson = require "cjson"
+local redis = require "lib/redis"
+local utils = require "lib/utils"
+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 _M = {};
+
+--- Request handler for routing tenant calls appropriately
+function _M.requestHandler()
+  local requestMethod = ngx.req.get_method()
+  if requestMethod == "GET" then
+    getTenants()
+  elseif requestMethod == "PUT" then
+    addTenant()
+  elseif requestMethod == "POST" then
+    addTenant()
+  elseif requestMethod == "DELETE" then
+    deleteTenant()
+  else
+    ngx.status = 400
+    ngx.say("Invalid verb")
+  end
+end
+
+--- Add a tenant to the Gateway
+-- PUT /v1/tenants
+-- body:
+-- {
+--    "namespace": *(String) tenant namespace
+--    "instance": *(String) tenant instance
+-- }
+function addTenant()
+  -- Open connection to redis or use one from connection pool
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  -- Check for tenant id and use existingTenant if it already exists in redis
+  local existingTenant = checkForExistingTenant(red)
+  -- 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)
+  -- Check for tenant id in JSON body
+  if existingTenant == nil and decoded.id ~= nil then
+    existingTenant = redis.getTenant(red, decoded.id)
+    if existingTenant == nil then
+      request.err(404, utils.concatStrings({"Unknown Tenant id ", decoded.id}))
+    end
+  end
+  -- Error checking
+  local res, err = utils.tableContainsAll(decoded, {"namespace", "instance"})
+  if res == false then
+    request.err(err.statusCode, err.message)
+  end
+  -- Return tenant object
+  local uuid = existingTenant ~= nil and existingTenant.id or utils.uuid()
+  local tenantObj = {
+    id = uuid,
+    namespace = decoded.namespace,
+    instance = decoded.instance
+  }
+  tenantObj = redis.addTenant(red, uuid, tenantObj)
+  redis.close(red)
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, tenantObj)
+end
+
+--- Check for tenant id from uri and use existing tenant if it already exists in redis
+-- @param red Redis client instance
+function checkForExistingTenant(red)
+  local id = ngx.var.tenant_id
+  local existing
+  -- Get object from redis
+  if id ~= nil and id ~= '' then
+    existing = redis.getTenant(red, id)
+    if existing == nil then
+      request.err(404, utils.concatStrings({"Unknown Tenant id ", id}))
+    end
+  end
+  return existing
+end
+
+--- Get one or all tenants from the gateway
+-- GET /v1/tenants
+function getTenants()
+  local queryParams = ngx.req.get_uri_args()
+  local id = ngx.var.tenant_id
+  if id == nil or id == '' then
+    getAllTenants(queryParams)
+  else
+    local query = ngx.var.query
+    if (query ~= nil and query ~= '') then
+      if query ~= "apis" then
+        request.err(400, "Invalid request")
+      else
+        getTenantAPIs(id, queryParams)
+      end
+    else
+      getTenant(id)
+    end
+  end
+end
+
+--- Get all tenants in redis
+-- @param queryParams object containing optional query parameters
+function getAllTenants(queryParams)
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  local tenants = redis.getAllTenants(red)
+  redis.close(red)
+  local tenantList
+  if next(queryParams) ~= nil then
+    tenantList = filterTenants(tenants, queryParams);
+  end
+  if tenantList == nil then
+    tenantList = {}
+    for k, v in pairs(tenants) do
+      if k%2 == 0 then
+        tenantList[#tenantList+1] = cjson.decode(v)
+      end
+    end
+    tenantList = cjson.encode(tenantList)
+  end
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, tenantList)
+end
+
+--- Filter tenants based on query parameters
+-- @param tenants list of tenants
+-- @param queryParams query parameters to filter tenants
+function filterTenants(tenants, queryParams)
+  local namespace = queryParams['filter[where][namespace]']
+  local instance = queryParams['filter[where][instance]']
+  -- missing or invalid query parameters
+  if (namespace == nil and instance == nil) or (instance ~= nil and namespace == nil) then
+    return nil
+  end
+  -- filter tenants
+  local tenantList = {}
+  for k, v in pairs(tenants) do
+    if k%2 == 0 then
+      local tenant = cjson.decode(v)
+      if (namespace ~= nil and instance == nil and tenant.namespace == namespace) or
+         (namespace ~= nil and instance ~= nil and tenant.namespace == namespace and tenant.instance == instance) then
+        tenantList[#tenantList+1] = tenant
+      end
+    end
+  end
+  return cjson.encode(tenantList)
+end
+
+--- Get tenant by its id
+-- @param id tenant id
+function getTenant(id)
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  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
+-- @param queryParams object containing optional query parameters
+function getTenantAPIs(id, queryParams)
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  local apis = redis.getAllAPIs(red)
+  redis.close(red)
+  local apiList
+  if next(queryParams) ~= nil then
+    apiList = filterTenantAPIs(id, apis, queryParams);
+  end
+  if apiList == nil then
+    apiList = {}
+    for k, v in pairs(apis) 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)
+  end
+  ngx.header.content_type = "application/json; charset=utf-8"
+  request.success(200, apiList)
+end
+
+--- Filter apis based on query paramters
+-- @param queryParams query parameters to filter apis
+function filterTenantAPIs(id, apis, queryParams)
+  local basePath = queryParams['filter[where][basePath]']
+  local name = queryParams['filter[where][name]']
+  -- missing query parameters
+  if (basePath == nil and name == nil)then
+    return nil
+  end
+  -- filter apis
+  local apiList = {}
+  for k, v in pairs(apis) do
+    if k%2 == 0 then
+      local api = cjson.decode(v)
+      if api.tenantId == id and
+        ((basePath ~= nil and name == nil and api.basePath == basePath) or
+        (name ~= nil and basePath == nil and api.name == name) or
+        (basePath ~= nil and name ~= nil and api.basePath == basePath and api.name == name)) then
+          apiList[#apiList+1] = api
+      end
+    end
+  end
+  return cjson.encode(apiList)
+end
+
+--- Delete tenant from gateway
+-- DELETE /v1/tenants/<id>
+function deleteTenant()
+  local id = ngx.var.tenant_id
+  if id == nil or id == '' then
+    request.err(400, "No id specified.")
+  end
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  local tenant = redis.getTenant(red, id)
+  if tenant == nil then
+    request.err(404, utils.concatStrings({"Unknown tenant id ", id}))
+  end
+  redis.deleteTenant(red, id)
+  redis.close(red)
+  request.success(200, {})
+end
+
+return _M
\ No newline at end of file
diff --git a/api-gateway-config/scripts/lua/metrics/MetricsBuffer.lua b/api-gateway-config/scripts/lua/metrics/MetricsBuffer.lua
deleted file mode 100644
index 47d322b..0000000
--- a/api-gateway-config/scripts/lua/metrics/MetricsBuffer.lua
+++ /dev/null
@@ -1,165 +0,0 @@
--- Copyright (c) 2015 Adobe Systems Incorporated. All rights reserved.
---
---   Permission is hereby granted, free of charge, to any person obtaining a
---   copy of this software and associated documentation files (the "Software"),
---   to deal in the Software without restriction, including without limitation
---   the rights to use, copy, modify, merge, publish, distribute, sublicense,
---   and/or sell copies of the Software, and to permit persons to whom the
---   Software is furnished to do so, subject to the following conditions:
---
---   The above copyright notice and this permission notice shall be included in
---   all copies or substantial portions of the Software.
---
---   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
---   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
---   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
---   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
---   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
---   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
---   DEALINGS IN THE SOFTWARE.
-
--- Base class for analytics with functions for calculating counts,timers
--- and updating the cache with data on every service call.
---
---
---
--- Usage in Nginx conf:
---
--- location api-gateway-stats {
---    allow 127.0.0.1;
---    deny all;
---    content_by_lua '
---      local MetricsBuffer = require "metrics.MetricsBuffer"
---      local metrics = MetricsBuffer:new()
---      local json = assert( metrics:toJson(), "Could not read metrics")
---      ngx.say( json )
---    ';
--- }
-
-local cjson = require "cjson"
-
-local M = {}
-
-function M:new(o)
-    o = o or {}
-    setmetatable(o, self)
-    self.__index = self
-    return o
-end
-
---- Increments a counter with the given <value>
--- @param metric The name of the metrics
--- @param value The value to intecrement with
---
-function M:count(metric, value)
-    local localMetrics = ngx.shared.stats_counters
-    local r
-    if ( localMetrics == nil ) then
-        ngx.log(ngx.ERROR, "Please define 'lua_shared_dict stats_counters 50m;' in http block")
-        return nil
-    end
-
-    value = tonumber(value) or 0
-    if value == 0 then -- to exit when nil/zero
-        return 0
-    end
-
-    r = localMetrics:incr(metric, value)
-    if ( r == nil ) then
-        localMetrics:add(metric, value)
-        return value
-    end
-    return r
-end
-
---- Adds a new timer value. Unline counters, timers are averaged when flushed
--- @param metric The name of the metrics
--- @param value A new value for the timer
---
-function M:timer(metric, value)
-    local localMetrics = ngx.shared.stats_timers
-    local r, counter
-    if ( localMetrics == nil ) then
-        ngx.log(ngx.ERROR, "Please define 'lua_shared_dict stats_timers 50m;' in http block")
-        return nil
-    end
-
-    value = tonumber(value) or -2
-
-    -- Timers are counting only the positive values, by convention
-    if ( value < 0 ) then
-        return value
-    end
-
-    r, counter = localMetrics:get(metric)
-    if ( r == nil ) then
-        localMetrics:set(metric, value, 0, 1)
-        return value
-    end
-    -- FIXES: attempt to perform arithmetic on local 'counter' (a nil value)
-    if ( counter == nil ) then
-        counter = 0
-    end
-    -- Adding the timers and increamenting the counter to compute avg later
-    counter = counter + 1
-    r = r + value
-    localMetrics:set(metric, r, 0, counter)
-    return r
-end
-
-function M:getJsonFor( metric_type )
-   -- convert shared_dict to table
-    local localMetrics = ngx.shared[metric_type]
-    if ( localMetrics == nil ) then
-        return nil
-    end
-    local keys = localMetrics:get_keys(1024)
-    local counter
-    local jsonObj = {}
-    local count = 0
-    for i,metric in pairs(keys) do
-        local value, counter = localMetrics:get(metric)
-        if(counter == nil) then
-            counter = 0
-        end
-        -- check if avg needs to be computed
-        if(counter == 1 or counter == 0) then
-            jsonObj[metric] = value
-        end
-        if(counter >= 2) then
-            jsonObj[metric] = math.floor(value/counter)
-        end
-        -- mark item as expired
-        localMetrics:set(metric, 0, 0.001,0)
-        count = i
-    end
-    return cjson.encode(jsonObj),count,jsonObj
-end
-
-function M:toJson( flushExpiredMetrics )
-    local counters, count_counters, counterObject = self:getJsonFor("stats_counters")
-    local timers, count_timers, timerObject = self:getJsonFor("stats_timers")
-    local flush = flushExpiredMetrics or true
-
-    -- ngx.log(ngx.INFO, "Wrote " .. count_counters .. " counters and " .. count_timers .. " timers.")
-
-    if ( flush == true ) then
-        self:flushExpiredKeys()
-    end
-    return "{\"counters\":" .. counters .. ",\"timers\":" .. timers .. "}", counterObject, timerObject
-end
-
-function M:flushExpiredKeys()
-    local metrics = ngx.shared.stats_counters
-    if ( metrics ~= nil ) then
-        metrics:flush_expired()
-    end
-
-    metrics = ngx.shared.stats_timers
-    if ( metrics ~= nil ) then
-        metrics:flush_expired()
-    end
-end
-
-return M
-
diff --git a/api-gateway-config/scripts/lua/metrics/MetricsCollector.lua b/api-gateway-config/scripts/lua/metrics/MetricsCollector.lua
deleted file mode 100644
index 624fb1f..0000000
--- a/api-gateway-config/scripts/lua/metrics/MetricsCollector.lua
+++ /dev/null
@@ -1,185 +0,0 @@
--- Copyright (c) 2015 Adobe Systems Incorporated. All rights reserved.
---
---   Permission is hereby granted, free of charge, to any person obtaining a
---   copy of this software and associated documentation files (the "Software"),
---   to deal in the Software without restriction, including without limitation
---   the rights to use, copy, modify, merge, publish, distribute, sublicense,
---   and/or sell copies of the Software, and to permit persons to whom the
---   Software is furnished to do so, subject to the following conditions:
---
---   The above copyright notice and this permission notice shall be included in
---   all copies or substantial portions of the Software.
---
---   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
---   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
---   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
---   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
---   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
---   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
---   DEALINGS IN THE SOFTWARE.
-
--- Records the metrics for the current request.
---
--- # Sample StatsD messages
--- # pub.publisher_name.consumer.consumer_name.app.application_name.service.service_name.prod.region.useast.request.device_links.POST.200.count
--- # pub.publisher_name.consumer.consumer_name.app.application_name.service.service_name.prod.region.useast.request.device_links.POST.200.responseTime
--- # pub.publisher_name.consumer.consumer_name.app.application_name.service.service_name.prod.region.useast.request.device_links.POST.200.upstreamResponseTime
--- # pub.publisher_name.consumer.consumer_name.app.application_name.service.service_name.prod.region.useast.request.validate_request.GET.200.responseTime
---
--- NOTE: by default it logs the root-path of the request. If the root path is used for versioning ( i.e. v1.0 ) there is the property $metric_path that
--- can be set on the location block in order to override the root-path
---
--- Created by IntelliJ IDEA.
--- User: nramaswa
--- Date: 3/14/14
--- Time: 12:45 AM
--- To change this template use File | Settings | File Templates.
---
-local MetricsCls = require "metrics.MetricsBuffer"
-local metrics = MetricsCls:new()
-
-local M = {}
-
-function M:new(o)
-    o = o or {}
-    setmetatable(o, self)
-    self.__index = self
-    return o
-end
-
--- To replace . with _
-local function normalizeString(input)
-    return input:gsub("%.", "_")
-end
-
-local function getPathForMetrics(serviceName)
-    local user_defined_path = ngx.var.metrics_path
-    if user_defined_path ~= nil and #user_defined_path < 2 then
-        user_defined_path = nil
-    end
-
-    local request_uri = user_defined_path or ngx.var.uri or "/" .. serviceName
-    local pattern = "(?:\\/?\\/?)([^\\/:]+)" -- To get the first part of the request uri excluding content after :
-    local requestPathFromURI, err = ngx.re.match(request_uri, pattern)
-    local requestPath = serviceName -- default value
-
-    if err then
-        ngx.log(ngx.WARN, "Assigned requestPath as serviceName due to error in extracting requestPathFromURI: ", err)
-    end
-
-    if requestPathFromURI then
-        if requestPathFromURI[1] then
-            requestPath = requestPathFromURI[1]
-            requestPath = requestPath:gsub("%.", "_")
-            --ngx.log(ngx.INFO, "\n the extracted requestPath::"..requestPath)
-        end
-    end
-    return requestPath
-end
-
-function M:logCurrentRequest()
-    -- Variables used in the bucket path
-    local publisherName = ngx.var.publisher_org_name or "undefined"
-    local consumerName = ngx.var.consumer_org_name or "undefined"
-    local appName = ngx.var.app_name or "undefined"
-    local serviceName = ngx.var.service_name or "undefined"
-    local realm = ngx.var.service_env or ngx.var.realm or "sandbox"
-    local requestMethod = ngx.var.request_method or "undefined"
-    local status = ngx.var.status or "0"
-    local validateRequestStatus = ngx.var.validate_request_status or "0"
-
-    -- Values for metrics - converted tonumber() later
-    local bucketCountValue = 1
-    local requestTime = tonumber(ngx.var.request_time) or -1
-    local upstreamResponseTime = tonumber(ngx.var.upstream_response_time) or -1
-    local validateTime = tonumber(ngx.var.validate_request_response_time) or -1
-    local rgnName = ngx.var.aws_region or "undefined"
-    local bytesSent = tonumber(ngx.var.bytes_sent) or -1
-    local bytesReceived = tonumber(ngx.var.request_length) or -1
-
-    -- ---------------------------------------------------------------------- --
-    -- ------------- Logging for all the requests            ---------------  --
-    -- ---------------------------------------------------------------------- --
-
-    local bucket = "publisher." .. normalizeString(publisherName) ..
-            ".consumer." .. normalizeString(consumerName) ..
-            ".application." .. normalizeString(appName) ..
-            ".service." .. normalizeString(serviceName) ..
-            "." .. realm ..
-            ".region." .. rgnName ..
-            ".request.";
-    local validate_request_response_time = bucket .. "validate_request." .. validateRequestStatus .. ".responseTime";
-
-    local requestPath = getPathForMetrics(serviceName)
-
-    local bytes_sent = bucket .. "bytesSent";
-    local bytes_received = bucket .. "bytesReceived";
-
-
-    --bandwidth data - update only if its greater than zero to sum up all calls
-    if bytesSent > 0 then
-        metrics:count(bytes_sent, bytesSent)
-    end
-    if bytesReceived > 0 then
-        metrics:count(bytes_received, bytesReceived)
-    end
-
-    -- log validate timer entry only if its passed
-    if validateTime >= 0 then
-        metrics:timer(validate_request_response_time, validateTime)
-    end
-
-    -- ---------------------------------------------------------------------- --
-    -- ------------- Non-blocked requests related logging    ---------------  --
-    -- ---------------------------------------------------------------------- --
-
-    -- Choosing log buckets in statsd based on validation success/failure in the gateway
-    if (validateRequestStatus == "200" or validateRequestStatus == "0" or validateRequestStatus == 200 or validateRequestStatus == "na") then
-        local cc_bucket = bucket ..
-                requestPath .. "." ..
-                requestMethod .. "." .. status;
-
-        local cc_response_time = cc_bucket .. ".responseTime";
-        local cc_upstream_response_time = cc_bucket .. ".upstreamResponseTime";
-
-        local hit_count_for_bucket = cc_bucket .. ".count";
-
-
-        --increament the count for all the calls
-        metrics:count(hit_count_for_bucket, bucketCountValue)
-        -- timers
-        if requestTime >= 0 then
-            metrics:timer(cc_response_time, requestTime)
-        end
-        --timer data - update only if its greater than or = zero as its avging all calls
-        if upstreamResponseTime >= 0 then
-            metrics:timer(cc_upstream_response_time, upstreamResponseTime)
-        end
-        return
-    end
-
-    -- ---------------------------------------------------------------------- --
-    -- ------------- Blocked requests related logging        ---------------  --
-    -- ---------------------------------------------------------------------- --
-
-    local blocked_bucket = bucket .. "_blocked_"
-
-    local code_count_bucket = blocked_bucket .. "." .. validateRequestStatus .. ".count"
-    local bytes_sent_bucket = blocked_bucket .. ".bytesSent"
-    local bytes_received_bucket = blocked_bucket .. ".bytesReceived"
-
-    --increament the count for all the calls
-    metrics:count(code_count_bucket, bucketCountValue)
-
-    --bandwidth data - update only if its greater than zero to sum up all calls
-    if bytesSent > 0 then
-        metrics:count(bytes_sent_bucket, bytesSent)
-    end
-    if bytesReceived > 0 then
-        metrics:count(bytes_received_bucket, bytesReceived)
-    end
-end
-
-
-return M
-
diff --git a/api-gateway-config/scripts/lua/metrics/factory.lua b/api-gateway-config/scripts/lua/metrics/factory.lua
deleted file mode 100644
index d71d504..0000000
--- a/api-gateway-config/scripts/lua/metrics/factory.lua
+++ /dev/null
@@ -1,37 +0,0 @@
--- Copyright (c) 2015 Adobe Systems Incorporated. All rights reserved.
---
---   Permission is hereby granted, free of charge, to any person obtaining a
---   copy of this software and associated documentation files (the "Software"),
---   to deal in the Software without restriction, including without limitation
---   the rights to use, copy, modify, merge, publish, distribute, sublicense,
---   and/or sell copies of the Software, and to permit persons to whom the
---   Software is furnished to do so, subject to the following conditions:
---
---   The above copyright notice and this permission notice shall be included in
---   all copies or substantial portions of the Software.
---
---   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
---   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
---   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
---   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
---   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
---   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
---   DEALINGS IN THE SOFTWARE.
-
---
--- A factory to initialize the methods that can be used for capturing usage data
--- This class is provided as an example
--- User: ddascal
---
-
-local MetricsCollector = require "metrics.MetricsCollector"
-local collector = MetricsCollector:new()
-
-local function _captureUsageData()
-    return collector:logCurrentRequest()
-end
-
-return {
-    captureUsageData = _captureUsageData
-}
-
diff --git a/api-gateway-config/scripts/lua/policies/security.lua b/api-gateway-config/scripts/lua/policies/security.lua
index 8b845fc..b468a89 100644
--- a/api-gateway-config/scripts/lua/policies/security.lua
+++ b/api-gateway-config/scripts/lua/policies/security.lua
@@ -1,81 +1,15 @@
--- Copyright (c) 2016 IBM. All rights reserved.
---
---   Permission is hereby granted, free of charge, to any person obtaining a
---   copy of this software and associated documentation files (the "Software"),
---   to deal in the Software without restriction, including without limitation
---   the rights to use, copy, modify, merge, publish, distribute, sublicense,
---   and/or sell copies of the Software, and to permit persons to whom the
---   Software is furnished to do so, subject to the following conditions:
---
---   The above copyright notice and this permission notice shall be included in
---   all copies or substantial portions of the Software.
---
---   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
---   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
---   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
---   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
---   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
---   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
---   DEALINGS IN THE SOFTWARE.
-
---- @module mapping
--- Process mapping object, turning implementation details into request transformations
--- @author Cody Walker (cmwalker), Alex Song (songs)
-
-local redis = require "lib/redis"
-local utils = require "lib/utils"
-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 _M = {}
 
---- Validate that the given subscription is in redis
--- @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
--- @param scope scope of the subscription
--- @param apiKey the subscription api key
--- @param return boolean value indicating if the subscription exists in redis
-function validateAPIKey(tenant, gatewayPath, apiId, scope, apiKey)
-  -- Open connection to redis or use one from connection pool
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
-  local k
-  if scope == 'tenant' then
-    k = utils.concatStrings({'subscriptions:tenant:', tenant})
-  elseif scope == 'resource' then
-    k = utils.concatStrings({'subscriptions:tenant:', tenant, ':resource:', gatewayPath})
-  elseif scope == 'api' then
-    k = utils.concatStrings({'subscriptions:tenant:', tenant, ':api:', apiId})
-  end
-  k = utils.concatStrings({k, ':key:', apiKey})
-  local exists = red:exists(k)
-  redis.close(red)
-  return exists == 1
-end
+local utils = require "lib/utils"
 
---- Process the security object
--- @param securityObj security object from nginx conf file
--- @return apiKey api key for the subscription
-function processAPIKey(securityObj)
-  local tenant = ngx.var.tenant
-  local gatewayPath = ngx.var.gatewayPath
-  local apiId = ngx.var.apiId
-  local scope = securityObj.scope
-  local header = (securityObj.header == nil) and 'x-api-key' or securityObj.header
-  local apiKey = ngx.var[utils.concatStrings({'http_', header}):gsub("-", "_")]
-  if not apiKey then
-    request.err(401, utils.concatStrings({'API key header "', header, '" is required.'}))
-  end
-  local ok = validateAPIKey(tenant, gatewayPath, apiId, scope, apiKey)
+function process(securityObj)
+  local ok, result = pcall(require, utils.concatStrings({'policies/security/', securityObj.type}))
   if not ok then
-    request.err(401, 'Invalid API key.')
-  end
-  return apiKey
+    ngx.err(500, 'An unexpected error ocurred while processing the security policy') 
+  end 
+  result.process(securityObj)
 end
 
-_M.processAPIKey = processAPIKey
+_M.process = process
 
-return _M
+return _M 
diff --git a/api-gateway-config/scripts/lua/policies/security/apikey.lua b/api-gateway-config/scripts/lua/policies/security/apikey.lua
new file mode 100644
index 0000000..2178c55
--- /dev/null
+++ b/api-gateway-config/scripts/lua/policies/security/apikey.lua
@@ -0,0 +1,81 @@
+
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+--   Permission is hereby granted, free of charge, to any person obtaining a
+--   copy of this software and associated documentation files (the "Software"),
+--   to deal in the Software without restriction, including without limitation
+--   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+--   and/or sell copies of the Software, and to permit persons to whom the
+--   Software is furnished to do so, subject to the following conditions:
+--
+--   The above copyright notice and this permission notice shall be included in
+--   all copies or substantial portions of the Software.
+--
+--   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+--   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+--   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+--   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+--   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+--   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+--   DEALINGS IN THE SOFTWARE.
+
+--- @module mapping
+-- Process mapping object, turning implementation details into request transformations
+-- @author Cody Walker (cmwalker), Alex Song (songs)
+
+local redis = require "lib/redis"
+local utils = require "lib/utils"
+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 _M = {}
+
+--- Validate that the given subscription is in redis
+-- @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
+-- @param scope scope of the subscription
+-- @param apiKey the subscription api key
+-- @param return boolean value indicating if the subscription exists in redis
+function validate(tenant, gatewayPath, apiId, scope, apiKey)
+  -- Open connection to redis or use one from connection pool
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+  local k
+  if scope == 'tenant' then
+    k = utils.concatStrings({'subscriptions:tenant:', tenant})
+  elseif scope == 'resource' then
+    k = utils.concatStrings({'subscriptions:tenant:', tenant, ':resource:', gatewayPath})
+  elseif scope == 'api' then
+    k = utils.concatStrings({'subscriptions:tenant:', tenant, ':api:', apiId})
+  end
+  k = utils.concatStrings({k, ':key:', apiKey})
+  local exists = red:exists(k)
+  redis.close(red)
+  return exists == 1
+end
+
+--- Process the security object
+-- @param securityObj security object from nginx conf file
+-- @return apiKey api key for the subscription
+function process(securityObj)
+  local tenant = ngx.var.tenant
+  local gatewayPath = ngx.var.gatewayPath
+  local apiId = ngx.var.apiId
+  local scope = securityObj.scope
+  local header = (securityObj.header == nil) and 'x-api-key' or securityObj.header
+  local apiKey = ngx.var[utils.concatStrings({'http_', header}):gsub("-", "_")]
+  if not apiKey then
+    request.err(401, utils.concatStrings({'API key header "', header, '" is required.'}))
+  end
+  local ok = validate(tenant, gatewayPath, apiId, scope, apiKey)
+  if not ok then
+    request.err(401, 'Invalid API key.')
+  end
+  return apiKey
+end
+
+_M.process = process
+return _M
diff --git a/api-gateway-config/scripts/lua/routing.lua b/api-gateway-config/scripts/lua/routing.lua
index 005e35e..62ce0e6 100644
--- a/api-gateway-config/scripts/lua/routing.lua
+++ b/api-gateway-config/scripts/lua/routing.lua
@@ -59,7 +59,7 @@
       -- Check if auth is required
       local apiKey
       if (opFields.security and opFields.security.type ~= nil and string.lower(opFields.security.type) == 'apikey') then
-        apiKey = security.processAPIKey(opFields.security)
+        apiKey = security.process(opFields.security)
       end
       -- Parse backend url
       local u = url.parse(opFields.backendUrl)
diff --git a/api-gateway-config/scripts/python/logger/StatsdLogger.py b/api-gateway-config/scripts/python/logger/StatsdLogger.py
deleted file mode 100644
index af57f76..0000000
--- a/api-gateway-config/scripts/python/logger/StatsdLogger.py
+++ /dev/null
@@ -1,93 +0,0 @@
-#
-# Background worker that loads statistics from the Api-Gateway endpoint and sends them to StatsD.
-# Designed to be restarted every minute by a cronjob
-#
-import json, sys, socket, urllib2
-import argparse
-from threading import Timer
-
-class StatsCollectorWorker:
-
-    UDP_MAX_BUFFER_SIZE = 800
-
-    def __init__(self, statsRestEndpoint, statsd_host, statsd_port ):
-        print(("Starting collecting form [%s], sending to StatsD on [%s:%s]") % (gateway_uri, statsd_host, statsd_port) )
-        self.statsRestEndpoint = statsRestEndpoint
-        self.statsd_host = statsd_host
-        self.statsd_port = statsd_port
-        self.udp_buffer = ""
-
-    def getStatsFromUrl(self, url):
-        try:
-            stats_txt = urllib2.urlopen(url).read()
-            return json.loads(stats_txt)
-        except Exception as e:
-            print "Could not read stats", e
-            return None
-
-    def flushBuffer(self):
-        # print "Sending:", self.udp_buffer
-        self.udp_sock.send(self.udp_buffer)
-        self.udp_buffer = ''
-
-    def sentStatsFromCollection(self, metricCollection, metricType):
-        for i, item in enumerate(metricCollection):
-            # print i, item, metricCollection[item]
-            statsd_metric = ('%s:%s|%s' % (item, metricCollection[item],metricType)).encode("utf-8")
-            if self.udp_buffer.__len__() + statsd_metric.__len__() > StatsCollectorWorker.UDP_MAX_BUFFER_SIZE:
-                self.flushBuffer()
-            self.udp_buffer = ''.join([self.udp_buffer, "\n", statsd_metric])
-
-    def sendStats(self, obj):
-        # TODO: split statsd_host by space in order to support sending data to multiple statsd backends
-        try:
-            self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-            self.udp_sock.connect((self.statsd_host, self.statsd_port))
-        except Exception as e:
-            print "Could not connect to StatsD", e
-            return None
-        try:
-            self.sentStatsFromCollection(obj["timers"], "ms")
-            self.sentStatsFromCollection(obj["counters"], "c")
-            if self.udp_buffer.__len__() > 0:
-                self.flushBuffer()
-        except Exception as e:
-            print "Could not send statistics", e
-        self.udp_sock.close()
-
-    def getAndSendStats(self,nextInterval, maxRuns):
-        # Read the JSON from the gateway's endpoint
-        # TODO: add an exception in case JSON conversion fails
-        obj = self.getStatsFromUrl(self.statsRestEndpoint)
-        if obj is not None:
-            self.sendStats(obj)
-
-        # print ("Stats processed. %s runs left" % (maxRuns))
-        if maxRuns <= 0:
-            return None
-        t = Timer(nextInterval, self.getAndSendStats, [nextInterval, maxRuns - 1])
-        t.start()
-
-
-statsd_host = "api-gateway-graphite"
-statsd_port = int("8125")
-gateway_uri = "http://127.0.0.1/api-gateway-stats"
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser()
-    parser.add_argument('-s', '--statsd-host', dest='statsd_host', required=True)
-    parser.add_argument('-p', '--statsd-port', dest='statsd_port')
-    parser.add_argument('-g', '--gateway-uri', dest='gateway_uri')
-    args = parser.parse_args()
-    statsd_host = args.statsd_host or statsd_host
-    statsd_port = int(args.statsd_port or statsd_port)
-    gateway_uri = args.gateway_uri or gateway_uri
-
-collector = StatsCollectorWorker(statsRestEndpoint=gateway_uri, statsd_host=statsd_host, statsd_port=statsd_port)
-# set the collector to run each 3 seconds , max 18 times
-collector.getAndSendStats(nextInterval=3,maxRuns=18)
-
-# Sample StatsD messages
-# pub.publisher_name.consumer.consumer_name.app.application_name.service.service_name.prod.region.useast.request.device_links.POST.200.count
-# pub.publisher_name.consumer.consumer_name.app.application_name.service.service_name.prod.region.useast.request.device_links.POST.200.responseTime
-# pub.publisher_name.consumer.consumer_name.app.application_name.service.service_name.prod.region.useast.request.device_links.POST.200.upstreamResponseTime
-# pub.publisher_name.consumer.consumer_name.app.application_name.service.service_name.prod.region.useast.request.validate_request.GET.200.responseTime
diff --git a/doc/routes.md b/doc/routes.md
index dc9a2a5..a2bb65a 100644
--- a/doc/routes.md
+++ b/doc/routes.md
@@ -237,7 +237,7 @@
 
 
 ## Subscriptions
-### PUT /subscriptions
+### PUT /v1/subscriptions
 Add/update an api key for the specified tenant, resource, or api.
 
 _body:_
@@ -256,7 +256,7 @@
 Subscription created.
 ```
 
-### DELETE /subscriptions
+### DELETE /v1/subscriptions
 Delete an api key associated with the specified tenant, resource or api.
 
 _body:_
diff --git a/init.sh b/init.sh
index aa7269f..6a41aa1 100755
--- a/init.sh
+++ b/init.sh
@@ -54,8 +54,7 @@
 if [[ -n "${redis_host}" && -n "${redis_port}" ]]; then
     sleep 1  # sleep until api-gateway is set up
     tail -f /var/log/api-gateway/access.log -f /var/log/api-gateway/error.log \
-         -f /var/log/api-gateway/gateway_error.log -f /var/log/api-gateway/management.log &
-    curl -s http://0.0.0.0:9000/v1/subscribe # subscribe to redis key changes for routes
+         -f /var/log/api-gateway/gateway_error.log -f /var/log/api-gateway/management.log
 else
     echo "REDIS_HOST and/or REDIS_PORT not defined"
 fi