Merge pull request #31 from openwhisk/develop

New subscribe logic and healthcheck
diff --git a/.gitignore b/.gitignore
index bbb2fb9..d0a944a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@
 *.swp
 *.swo
 
+# Concourse
+secrets.yml
diff --git a/Dockerfile b/Dockerfile
index 69f9723..81fb111 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,10 +8,9 @@
 
 # install dependencies
 RUN apk update \
-    && apk add gcc tar libtool zlib jemalloc jemalloc-dev perl \ 
+    && apk add gcc tar libtool zlib jemalloc jemalloc-dev perl \
     make musl-dev openssl-dev pcre-dev g++ zlib-dev curl python \
-    perl-test-longstring perl-list-moreutils perl-http-message \
-    geoip-dev nodejs
+    perl-test-longstring perl-list-moreutils perl-http-message geoip-dev
 
 # openresty build
 ENV OPENRESTY_VERSION=1.9.7.3 \
diff --git a/api-gateway-config/conf.d/management_apis.conf b/api-gateway-config/conf.d/management_apis.conf
index eccdcb1..8c8338c 100644
--- a/api-gateway-config/conf.d/management_apis.conf
+++ b/api-gateway-config/conf.d/management_apis.conf
@@ -83,21 +83,42 @@
                  ngx.status = 400
                  ngx.say("Invalid verb")
              end
-
          }
-     }
+    }
 
-    location /subscribe {
+    location /v1/sync {
         access_by_lua_block {
             local mgmt = require("management")
-            mgmt.subscribe()
+            local requestMethod = ngx.req.get_method()
+            if requestMethod == "GET" then
+                mgmt.sync()
+            else
+                ngx.say("Invalid verb")
+            end
         }
     }
 
-    location /unsubscribe {
+    location /v1/subscribe {
         access_by_lua_block {
             local mgmt = require("management")
-            mgmt.unsubscribe()
+            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()
+            else
+                ngx.say("Invalid verb")
+            end
         }
     }
 }
diff --git a/api-gateway-config/scripts/lua/lib/filemgmt.lua b/api-gateway-config/scripts/lua/lib/filemgmt.lua
index ca1d793..f1a6e1c 100644
--- a/api-gateway-config/scripts/lua/lib/filemgmt.lua
+++ b/api-gateway-config/scripts/lua/lib/filemgmt.lua
@@ -73,8 +73,6 @@
   })
   file:write(location)
   file:close()
-  -- reload nginx to refresh conf files
-  os.execute("/usr/local/sbin/nginx -s reload")
   return fileLocation
 end
 
@@ -86,8 +84,6 @@
 function _M.deleteResourceConf(baseConfDir, tenant, gatewayPath)
   local fileLocation = utils.concatStrings({baseConfDir, tenant, "/", gatewayPath, ".conf"})
   os.execute(utils.concatStrings({"rm -f ", fileLocation}))
-  -- reload nginx to refresh conf files
-  os.execute("/usr/local/sbin/nginx -s reload")
   return fileLocation
 end
 
diff --git a/api-gateway-config/scripts/lua/lib/logger.lua b/api-gateway-config/scripts/lua/lib/logger.lua
index c1e5680..a5fd418 100644
--- a/api-gateway-config/scripts/lua/lib/logger.lua
+++ b/api-gateway-config/scripts/lua/lib/logger.lua
@@ -21,6 +21,7 @@
 --- @module logger
 -- Module to handle logging in a single place
 -- @author Cody Walker (cmwalker), Alex Song (songs)
+local utils = require "lib/utils"
 
 local _M = {}
 
@@ -33,6 +34,13 @@
 --- Handle debug stream to stdout
 -- @param s String to write to debug stream
 function _M.debug(s)
+  if s == nil then
+    s = "nil"
+  elseif type(s) == "table" then
+    s = utils.serializeTable(s)
+  elseif type(s) == "boolean" then
+    s = (s == true) and "true" or "false"
+  end
   os.execute("echo \"" .. s .. "\"")
 end
 
diff --git a/api-gateway-config/scripts/lua/lib/redis.lua b/api-gateway-config/scripts/lua/lib/redis.lua
index 62b2322..c2df16e 100644
--- a/api-gateway-config/scripts/lua/lib/redis.lua
+++ b/api-gateway-config/scripts/lua/lib/redis.lua
@@ -60,13 +60,13 @@
     connect, err = red:connect(host, port)
   end
   if not connect then
-    request.err(500, utils.concatStrings({"Failed to connect to redis: ", err}))  
+    request.err(500, utils.concatStrings({"Failed to connect to redis: ", err}))
   end
   -- Authenticate with Redis
   if password ~= nil and password ~= "" then
     local res, err = red:auth(password)
     if not res then
-      request.err(500, utils.concatStrings({"Failed to authenticate: ", err}))  
+      request.err(500, utils.concatStrings({"Failed to authenticate: ", err}))
     end
   end
   return red
@@ -90,11 +90,35 @@
 -- @param red Redis client instance
 -- @param id id of API
 -- @param apiObj the api to add
-function _M.addAPI(red, id, apiObj)
+-- @param existingAPI existing api to update
+function _M.addAPI(red, id, apiObj, existingAPI)
+  if existingAPI == nil then
+    local apis = _M.getAllAPIs(red)
+    -- Return error if api with basepath already exists
+    for apiId, obj in pairs(apis) do
+      if apiId%2 == 0 then
+        obj = cjson.decode(obj)
+        if obj.tenantId == apiObj.tenantId and obj.basePath == apiObj.basePath then
+          request.err(500, "basePath not unique for given tenant.")
+        end
+      end
+    end
+  else
+    -- Delete all resources for the existingAPI
+    local basePath = existingAPI.basePath:sub(2)
+    for path, v in pairs(existingAPI.resources) do
+      local gatewayPath = utils.concatStrings({basePath, ngx.escape_uri(path)})
+      local redisKey = utils.concatStrings({"resources:", existingAPI.tenantId, ":", ngx.unescape_uri(gatewayPath)})
+      _M.deleteResource(red, redisKey, REDIS_FIELD)
+    end
+  end
+  -- Add new API
+  apiObj = cjson.encode(apiObj):gsub("\\", "")
   local ok, err = red:hset("apis", id, apiObj)
   if not ok then
     request.err(500, utils.concatStrings({"Failed to save the API: ", err}))
   end
+  return apiObj
 end
 
 --- Get all APIs from redis
@@ -188,10 +212,32 @@
   if resourceObj == ngx.null then
     return nil
   end
-
   return resourceObj
 end
 
+--- Get all resource keys in redis
+-- @param red redis client instance
+function getAllResourceKeys(red)
+  -- Find all resourceKeys in redis
+  local resources, err = red:scan(0, "match", "resources:*:*")
+  if not resources then
+    request.err(500, util.concatStrings({"Failed to retrieve resource keys: ", err}))
+  end
+  local cursor = resources[1]
+  local resourceKeys = resources[2]
+  while cursor ~= "0" do
+    resources, err = red:scan(cursor, "match", "resources:*:*")
+    if not resources then
+      request.err(500, util.concatStrings({"Failed to retrieve resource keys: ", err}))
+    end
+    cursor = resources[1]
+    for k, v in pairs(resources[2]) do
+      resourceKeys[#resourceKeys + 1] = v
+    end
+  end
+  return resourceKeys
+end
+
 --- Delete resource in redis
 -- @param red redis client instance
 -- @param key redis resource key
@@ -204,6 +250,7 @@
   if resourceObj == ngx.null then
     request.err(404, "Resource doesn't exist.")
   end
+  -- Delete redis resource
   local ok, err = red:del(key)
   if not ok then
     request.err(500, utils.concatStrings({"Failed to delete the resource: ", err}))
@@ -221,10 +268,23 @@
 -- @param id id of tenant
 -- @param tenantObj the tenant to add
 function _M.addTenant(red, id, tenantObj)
+  local tenants = _M.getAllTenants(red)
+  -- Return tenant from redis if it already exists
+  for tenantId, obj in pairs(tenants) do
+    if tenantId%2 == 0 then
+      obj = cjson.decode(obj)
+      if obj.namespace == tenantObj.namespace and obj.instance == tenantObj.instance then
+        return cjson.encode(obj)
+      end
+    end
+  end
+  -- Add new tenant
+  tenantObj = cjson.encode(tenantObj)
   local ok, err = red:hset("tenants", id, tenantObj)
   if not ok then
     request.err(500, utils.concatStrings({"Failed to add the tenant: ", err}))
   end
+  return tenantObj
 end
 
 --- Get all tenants from redis
@@ -297,123 +357,88 @@
 ------- Pub/Sub with Redis --------
 -----------------------------------
 
+local syncStatus = false
+--- Sync with redis on startup and create conf files for resources that are already in redis
+-- @param red redis client instance
+function _M.syncWithRedis(red)
+  logger.debug("Sync with redis in progress...")
+  setSyncStatus(true)
+  local resourceKeys = getAllResourceKeys(red)
+  for k, resourceKey in pairs(resourceKeys) do
+    local prefix, tenant, gatewayPath = resourceKey:match("([^,]+):([^,]+):([^,]+)")
+    local resourceObj = _M.getResource(red, resourceKey, REDIS_FIELD)
+    filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
+  end
+  os.execute("/usr/local/sbin/nginx -s reload")
+  setSyncStatus(false)
+  logger.debug("All resources synced.")
+end
+
+function setSyncStatus(status)
+  syncStatus = status
+end
+
+function getSyncStatus()
+  return syncStatus
+end
+
 --- Subscribe to redis
 -- @param redisSubClient the redis client that is listening for the redis key changes
 -- @param redisGetClient the redis client that gets the changed resource to update the conf file
 function _M.subscribe(redisSubClient, redisGetClient)
-  -- create conf files for existing resources in redis
-  syncWithRedis(redisGetClient, ngx)
-  -- enable keyspace notifications
-  local ok, err = redisGetClient:config("set", "notify-keyspace-events", "KEA")
+  logger.debug("Subscribed to redis and listening for key changes...")
+  -- Subscribe to redis using psubscribe
+  local ok, err = redisSubClient:config("set", "notify-keyspace-events", "KEA")
   if not ok then
-    request.err(500, utils.concatStrings({"Failed setting notify-keyspace-events: ", err}))
+    request.err(500, utils.concatStrings({"Failed to subscribe to redis: ", err}))
   end
   ok, err = redisSubClient:psubscribe("__keyspace@0__:resources:*:*")
   if not ok then
     request.err(500, utils.concatStrings({"Failed to subscribe to redis: ", err}))
   end
-  ngx.say("\nSubscribed to redis and listening for key changes...")
-  ngx.flush(true)
-  subscribe(redisSubClient, redisGetClient, ngx)
-  ngx.exit(ngx.status)
-end
-
---- Sync with redis on startup and create conf files for resources that are already in redis
--- @param red redis client instance
-function syncWithRedis(red)
-  logger.debug("\nCreating nginx conf files for existing resources...")
-  local redisKeys, err = red:keys("*")
-  if not redisKeys then
-    request.err(500, util.concatStrings({"Failed to sync with Redis: ", err}))
-  end
-  -- Find all redis keys with "resources:*:*"
-  local resourcesExist = false
-  for k, redisKey in pairs(redisKeys) do
-    local index = 1
-    local tenant = ""
-    local gatewayPath = ""
-    for word in string.gmatch(redisKey, '([^:]+)') do
-      if index == 1 then
-        if word ~= "resources" then
-          break
-        else
-          resourcesExist = true
-          index = index + 1
-        end
-      else
-        if index == 2 then
-          tenant = word
-        elseif index == 3 then
-          gatewayPath = word
-          -- Create new conf file
-          local resourceObj = _M.getResource(red, redisKey, REDIS_FIELD)
-          local fileLocation = filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
-          logger.debug(utils.concatStrings({"Updated file: ", fileLocation}))
-        end
-        index = index + 1
-      end
-    end
-  end
-  if resourcesExist == false then
-    logger.debug("No existing resources.")
-  end
-end
-
---- Subscribe helper method
--- Starts a while loop that listens for key changes in redis
--- @param redisSubClient the redis client that is listening for the redis key changes
--- @param redisGetClient the redis client that gets the changed resource to update the conf file
-function subscribe(redisSubClient, redisGetClient)
+  -- Update nginx conf file when redis is updated
+  local redisUpdated = false
+  local startTime = ngx.now()
   while true do
     local res, err = redisSubClient:read_reply()
     if not res then
       if err ~= "timeout" then
-        ngx.say("Read reply error: ", err)
-        ngx.exit(ngx.status)
+        request.err(500, utils.concatStrings({"Failed to read from redis: ", err}))
       end
     else
-      local index = 1
-      local redisKey = ""
-      local tenant = ""
-      local gatewayPath = ""
-      for word in string.gmatch(res[3], '([^:]+)') do
-        if index == 2 then
-          redisKey = utils.concatStrings({redisKey, word, ":"})
-        elseif index == 3 then
-          tenant = word
-          redisKey = utils.concatStrings({redisKey, tenant, ":"})
-        elseif index == 4 then
-          gatewayPath = word
-          redisKey = utils.concatStrings({redisKey, gatewayPath})
-        end
-        index = index + 1
-      end
+      -- res[3] format is "__keyspace@0__:resources:<tenantId>:<gatewayPath>"
+      local keyspacePrefix, resourcePrefix, tenant, gatewayPath = res[3]:match("([^,]+):([^,]+):([^,]+):([^,]+)")
+      local redisKey = utils.concatStrings({resourcePrefix, ":", tenant, ":", gatewayPath})
       local resourceObj = _M.getResource(redisGetClient, redisKey, REDIS_FIELD)
       if resourceObj == nil then
-        local fileLocation = filemgmt.deleteResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath))
         logger.debug(utils.concatStrings({"Redis key deleted: ", redisKey}))
+        local fileLocation = filemgmt.deleteResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath))
         logger.debug(utils.concatStrings({"Deleted file: ", fileLocation}))
       else
-        local fileLocation = filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
         logger.debug(utils.concatStrings({"Redis key updated: ", redisKey}))
+        local fileLocation = filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
         logger.debug(utils.concatStrings({"Updated file: ", fileLocation}))
       end
+      redisUpdated = true
+    end
+    -- reload Nginx only if redis has been updated and it has been at least 1 second since last reload
+    local timeDiff = ngx.now() - startTime
+    if(redisUpdated == true and timeDiff >= 1) then
+      os.execute("/usr/local/sbin/nginx -s reload")
+      logger.debug("Nginx reloaded.")
+      redisUpdated = false
+      startTime = ngx.now()
     end
   end
-  ngx.exit(ngx.status)
 end
 
-
---- Unsubscribe from redis
--- @param red redis client instance
-function _M.unsubscribe(red)
-  local ok, err = red:unsubscribe("__keyspace@0__:resources:*:*")
-  if not ok then
-    request.err(500, utils.concatStrings({"Failed to unsubscribe to redis: ", err}))
+--- Get gateway sync status
+function _M.healthCheck()
+  if getSyncStatus() == true then
+    request.success(503, "Status: Gateway syncing.")
+  else
+    request.success(200, "Status: Gateway ready.")
   end
-  _M.close(red, ngx)
-  ngx.say("Unsubscribed from redis")
-  ngx.exit(ngx.status)
 end
 
 return _M
\ No newline at end of file
diff --git a/api-gateway-config/scripts/lua/lib/request.lua b/api-gateway-config/scripts/lua/lib/request.lua
index 0ef5999..9a61a3e 100644
--- a/api-gateway-config/scripts/lua/lib/request.lua
+++ b/api-gateway-config/scripts/lua/lib/request.lua
@@ -31,7 +31,7 @@
 -- @param msg error message
 function err(code, msg)
   ngx.status = code
-  ngx.print(utils.concatStrings({"Error: ", msg}))
+  ngx.say(utils.concatStrings({"Error: ", msg}))
   ngx.exit(ngx.status)
 end
 
@@ -40,7 +40,7 @@
 -- @param obj object to return
 function success(code, obj)
   ngx.status = code
-  ngx.print(obj)
+  ngx.say(obj)
   ngx.exit(ngx.status)
 end
 
diff --git a/api-gateway-config/scripts/lua/management.lua b/api-gateway-config/scripts/lua/management.lua
index a09ff51..1024330 100644
--- a/api-gateway-config/scripts/lua/management.lua
+++ b/api-gateway-config/scripts/lua/management.lua
@@ -24,7 +24,6 @@
 
 local cjson = require "cjson"
 local redis = require "lib/redis"
-local filemgmt = require "lib/filemgmt"
 local utils = require "lib/utils"
 local logger = require "lib/logger"
 local request = require "lib/request"
@@ -56,22 +55,9 @@
 function _M.addAPI()
   -- Open connection to redis or use one from connection pool
   local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
-  -- Check for api id and use existingAPI if it already exists in redis
+  -- Check for api id from uri and use existingAPI if it already exists in redis
   local uri = string.gsub(ngx.var.request_uri, "?.*", "")
-  local apiId, existingAPI
-  local index = 1
-  for word in string.gmatch(uri, '([^/]+)') do
-    if index == 3 then
-      apiId = word
-    end
-    index = index + 1
-  end
-  if apiId ~= nil then
-    existingAPI = redis.getAPI(red, apiId)
-    if existingAPI == nil then
-      request.err(404, utils.concatStrings({"Unknown API id ", apiId}))
-    end
-  end
+  local existingAPI = checkURIForExisting(red, uri, "api")
   -- Read in the PUT JSON Body
   ngx.req.read_body()
   local args = ngx.req.get_body_data()
@@ -80,6 +66,13 @@
   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
@@ -90,12 +83,7 @@
   end
   -- Format basePath
   local basePath = decoded.basePath:sub(1,1) == '/' and decoded.basePath:sub(2) or decoded.basePath
-  -- Add resources to redis and create nginx conf files
-  for path, resource in pairs(decoded.resources) do
-    local gatewayPath = utils.concatStrings({basePath, ngx.escape_uri(path)})
-    addResource(red, resource, gatewayPath, decoded.tenantId)
-  end
-  -- Return managedUrl object
+  -- 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
@@ -109,9 +97,13 @@
     resources = decoded.resources,
     managedUrl = managedUrl
   }
-  managedUrlObj = cjson.encode(managedUrlObj):gsub("\\", "")
   -- Add API object to redis
-  redis.addAPI(red, uuid, managedUrlObj)
+  managedUrlObj = redis.addAPI(red, uuid, managedUrlObj, existingAPI)
+  -- Add resources to redis
+  for path, resource in pairs(decoded.resources) do
+    local gatewayPath = utils.concatStrings({basePath, ngx.escape_uri(path)})
+    addResource(red, resource, gatewayPath, decoded.tenantId)
+  end
   redis.close(red)
   -- Return managed url object
   ngx.header.content_type = "application/json; charset=utf-8"
@@ -206,7 +198,6 @@
   end
   local resourceObj = redis.generateResourceObj(operations, apiId)
   redis.createResource(red, redisKey, REDIS_FIELD, resourceObj)
-  filemgmt.createResourceConf(BASE_CONF_DIR, tenantId, gatewayPath, resourceObj)
 end
 
 --- Get one or all APIs from the gateway
@@ -306,13 +297,14 @@
   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, ngx.escape_uri(path)})
     deleteResource(red, gatewayPath, api.tenantId)
   end
-  redis.deleteAPI(red, id)
   redis.close(red)
   request.success(200, {})
 end
@@ -324,7 +316,6 @@
 function deleteResource(red, gatewayPath, tenantId)
   local redisKey = utils.concatStrings({"resources:", tenantId, ":", ngx.unescape_uri(gatewayPath)})
   redis.deleteResource(red, redisKey, REDIS_FIELD)
-  filemgmt.deleteResourceConf(BASE_CONF_DIR, tenantId, gatewayPath)
 end
 
 -----------------------------
@@ -343,20 +334,7 @@
   local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
   -- Check for tenant id and use existingTenant if it already exists in redis
   local uri = string.gsub(ngx.var.request_uri, "?.*", "")
-  local tenantId, existingTenant
-  local index = 1
-  for word in string.gmatch(uri, '([^/]+)') do
-    if index == 3 then
-      tenantId = word
-    end
-    index = index + 1
-  end
-  if tenantId ~= nil then
-    existingTenant = redis.getTenant(red, tenantId)
-    if existingTenant == nil then
-      request.err(400, utils.concatStrings({"Unknown tenant id ", tenantId}))
-    end
-  end
+  local existingTenant = checkURIForExisting(red, uri, "tenant")
   -- Read in the PUT JSON Body
   ngx.req.read_body()
   local args = ngx.req.get_body_data()
@@ -365,6 +343,13 @@
   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
@@ -379,8 +364,7 @@
     namespace = decoded.namespace,
     instance = decoded.instance
   }
-  tenantObj = cjson.encode(tenantObj)
-  redis.addTenant(red, uuid, tenantObj)
+  tenantObj = redis.addTenant(red, uuid, tenantObj)
   redis.close(red)
   ngx.header.content_type = "application/json; charset=utf-8"
   request.success(200, tenantObj)
@@ -490,24 +474,27 @@
 ----- Pub/Sub with Redis -----
 ------------------------------
 
+--- Sync with redis
+-- GET /v1/sync
+function _M.sync()
+  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+  logger.debug(utils.concatStrings({"Connected to redis at ", REDIS_HOST, ":", REDIS_PORT}))
+  redis.syncWithRedis(red)
+  ngx.exit(200)
+end
+
 --- Subscribe to redis
--- GET /subscribe
+-- GET /v1/subscribe
 function _M.subscribe()
-  -- Initialize and connect to redis
   local redisGetClient = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
-  local redisSubClient = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 60000) -- read_reply will timeout every minute
-  logger.debug(utils.concatStrings({"\nConnected to redis at ", REDIS_HOST, ":", REDIS_PORT}))
+  local redisSubClient = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
   redis.subscribe(redisSubClient, redisGetClient)
   ngx.exit(200)
 end
 
---- Unsusbscribe to redis
--- GET /unsubscribe
-function _M.unsubscribe()
-  -- Initialize and connect to redis
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
-  redis.unsubscribe(red)
-  request.success(200, "Unsubscribed to redis")
+--- Get gateway sync status
+function _M.healthCheck()
+  redis.healthCheck()
 end
 
 ---------------------------
@@ -606,6 +593,38 @@
   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
diff --git a/api-gateway-config/tests/install-deps.sh b/api-gateway-config/tests/install-deps.sh
index d44356a..5f9d12d 100755
--- a/api-gateway-config/tests/install-deps.sh
+++ b/api-gateway-config/tests/install-deps.sh
@@ -1,13 +1,13 @@
-#!/bin/bash
+#!/bin/sh
 
 # Install global dependencies
 luarocks install busted
 luarocks install luacov
 # Install test dependencies
 mkdir -p lua_modules
-luarocks install --tree lua_modules lua-cjson
-luarocks install --tree lua_modules luabitop
-luarocks install --tree lua_modules luasocket
-luarocks install --tree lua_modules sha1
-luarocks install --tree lua_modules md5
-luarocks install --tree lua_modules fakeredis
\ No newline at end of file
+luarocks install --tree=lua_modules lua-cjson
+luarocks install --tree=lua_modules luabitop
+luarocks install --tree=lua_modules luasocket
+luarocks install --tree=lua_modules sha1
+luarocks install --tree=lua_modules md5
+luarocks install --tree=lua_modules fakeredis
diff --git a/api-gateway-config/tests/run-tests.sh b/api-gateway-config/tests/run-tests.sh
index 7d27b35..1eaa926 100755
--- a/api-gateway-config/tests/run-tests.sh
+++ b/api-gateway-config/tests/run-tests.sh
@@ -1,9 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # Run unit tests
-busted -c --output=TAP --helper=set_paths spec/test.lua
-
-# Generate code coverage report
-luacov ../scripts/lua/
-cat luacov.report.out
-rm luacov.report.out && rm luacov.stats.out
\ No newline at end of file
+busted --output=TAP --helper=set_paths spec/test.lua
\ No newline at end of file
diff --git a/api-gateway-config/tests/spec/test.lua b/api-gateway-config/tests/spec/test.lua
index b3e88a0..5547e61 100644
--- a/api-gateway-config/tests/spec/test.lua
+++ b/api-gateway-config/tests/spec/test.lua
@@ -44,16 +44,16 @@
     local code = 500
     local msg = 'Internal server error\n'
     request.err(code, msg)
-    assert.are.equal(ngx._body, 'Error: ' .. msg)
-    assert.are.equal(ngx._exit, code)
+    assert.are.equal('Error: ' .. msg .. '\n', ngx._body)
+    assert.are.equal(code, ngx._exit)
   end)
 
   it('should return correct success response', function()
     local code = 200
     local msg ='Success!\n'
     request.success(code, msg)
-    assert.are.equal(ngx._body, msg)
-    assert.are.equal(ngx._exit, code)
+    assert.are.equal(msg .. '\n', ngx._body)
+    assert.are.equal(code, ngx._exit)
   end)
 end)
 
@@ -231,6 +231,7 @@
     redis.deleteSubscription(red, key)
     assert.are.equal(false, red:exists(key))
   end)
+
 end)
 
 --TODO: filemgmt
@@ -240,8 +241,3 @@
 ---------------------------------------
 
 --TODO: mapping, rateLimit, security
-describe('Testing mapping module', function()
-  before_each(function()
-    _G.ngx = fakengx.new()
-  end)
-end)
diff --git a/concourse/pipeline.yml b/concourse/pipeline.yml
new file mode 100644
index 0000000..83c0bc1
--- /dev/null
+++ b/concourse/pipeline.yml
@@ -0,0 +1,37 @@
+---
+jobs:
+  - name: PR unit tests
+    public: true
+    serial: true
+    plan:
+      - get: apigateway
+        trigger: true
+      - put: apigateway
+        params:
+          path: apigateway
+          status: pending
+      - task: run-unit-tests
+        file: apigateway/concourse/tasks/run-unit-tests.yml
+        on_success:
+          put: apigateway
+          params:
+            path: apigateway
+            status: success
+        on_failure:
+          put: apigateway
+          params:
+            path: apigateway
+            status: failure
+
+resource_types:
+  - name: pull-request
+    type: docker-image
+    source:
+      repository: jtarchie/pr
+
+resources:
+  - name: apigateway
+    type: pull-request
+    source:
+      repo: openwhisk/apigateway
+      access_token: {{access_token}}
diff --git a/concourse/tasks/run-unit-tests.sh b/concourse/tasks/run-unit-tests.sh
new file mode 100755
index 0000000..015b307
--- /dev/null
+++ b/concourse/tasks/run-unit-tests.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -e
+
+apk add --update git
+
+cd apigateway/api-gateway-config/tests
+./install-deps.sh
+./run-tests.sh
\ No newline at end of file
diff --git a/concourse/tasks/run-unit-tests.yml b/concourse/tasks/run-unit-tests.yml
new file mode 100644
index 0000000..b5a0291
--- /dev/null
+++ b/concourse/tasks/run-unit-tests.yml
@@ -0,0 +1,12 @@
+---
+platform: linux
+
+image_resource:
+  type: docker-image
+  source: {repository: abaez/luarocks, tag: lua5.1}
+
+inputs:
+  - name: apigateway
+
+run:
+  path: apigateway/concourse/tasks/run-unit-tests.sh
diff --git a/init.sh b/init.sh
index b81d4d1..a553b75 100755
--- a/init.sh
+++ b/init.sh
@@ -53,7 +53,8 @@
 
 if [[ -n "${redis_host}" && -n "${redis_port}" ]]; then
     sleep 1  # sleep until api-gateway is set up
-    curl -s http://0.0.0.0:9000/subscribe # subscribe to redis key changes for routes
+    curl -s http://0.0.0.0:9000/v1/sync & # sync with redis
+    curl -s http://0.0.0.0:9000/v1/subscribe # subscribe to redis key changes for routes
 else
     echo "REDIS_HOST and/or REDIS_PORT not defined"
 fi