You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by mh...@apache.org on 2017/06/29 19:23:48 UTC

[incubator-openwhisk-apigateway] branch master updated: Snapshotting of tenant swagger in redis (#222)

This is an automated email from the ASF dual-hosted git repository.

mhamann pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-apigateway.git


The following commit(s) were added to refs/heads/master by this push:
     new f84a9d6  Snapshotting of tenant swagger in redis (#222)
f84a9d6 is described below

commit f84a9d6db4a563f9e8a55714060750cf6ce334c2
Author: Taylor King <ta...@gmail.com>
AuthorDate: Thu Jun 29 15:23:46 2017 -0400

    Snapshotting of tenant swagger in redis (#222)
    
    * basic tenant based snapshotting
    
    * add some tests
    
    * some basic management apis are working now
    
    * subscriptions endpoint works with snapshots
    
    * add a lock
    
    * tests passing
---
 api-gateway.conf                                |   2 +-
 scripts/lua/lib/dataStore.lua                   |  68 ++++++++++++----
 scripts/lua/lib/redis.lua                       | 103 ++++++++++++++++++++----
 scripts/lua/management/lib/resources.lua        |   3 +
 scripts/lua/management/lib/subscriptions.lua    |  42 +++-------
 scripts/lua/management/routes/apis.lua          |   4 +-
 scripts/lua/management/routes/subscriptions.lua |  66 ++++++++-------
 scripts/lua/routing.lua                         |  16 +++-
 tests/fakeredis.lua                             |  10 ++-
 tests/scripts/lua/lib/subscriptions.lua         |  16 ++--
 tests/scripts/lua/routing.lua                   |  16 ++++
 tests/scripts/lua/security.lua                  |  64 ++++++++++++++-
 12 files changed, 300 insertions(+), 110 deletions(-)

diff --git a/api-gateway.conf b/api-gateway.conf
index e206e48..4c18873 100644
--- a/api-gateway.conf
+++ b/api-gateway.conf
@@ -36,7 +36,7 @@ env PORT;
 
 
 env REDIS_RETRY_COUNT;
-
+env SNAPSHOTTING;
 env CACHING_ENABLED;
 env CACHE_SIZE;
 env CACHE_TTL;
diff --git a/scripts/lua/lib/dataStore.lua b/scripts/lua/lib/dataStore.lua
index 52aa773..764d870 100644
--- a/scripts/lua/lib/dataStore.lua
+++ b/scripts/lua/lib/dataStore.lua
@@ -1,8 +1,3 @@
-
-
-
-
-
 local DATASTORE = os.getenv( 'DATASTORE')
 local utils = require('lib/utils')
 
@@ -21,6 +16,13 @@ function DataStore:init()
   return o
 end
 
+function DataStore:setSnapshotId(tenant)
+  self.snapshotId = self.impl.getSnapshotId(self.ds, tenant)
+  self:lockSnapshot(snapshotId)
+  if self.snapshotId == ngx.null then
+    self.snapshotId = nil
+  end
+end
 -- right now just using this for the tests
 function DataStore:initWithDriver(ds)
 local o = {}
@@ -31,11 +33,16 @@ local o = {}
   return o
 end
 
+function DataStore:lockSnapshot(snapshotId)
+  return self.impl.lockSnapshot(self.ds, snapshotId)
+end
+
 function DataStore:close()
   return self.impl.close(self.ds)
 end
 
 function DataStore:addAPI(id, apiObj, existingAPI)
+  self:setSnapshotId(apiObj.tenantId)
   return self.impl.addAPI(self.ds, id, apiObj, existingAPI)
 end
 
@@ -52,7 +59,7 @@ function DataStore:deleteAPI(id)
 end
 
 function DataStore:resourceToApi(resource)
-  return self.impl.resourceToApi(self.ds, resource)
+  return self.impl.resourceToApi(self.ds, resource, self.snapshotId)
 end
 
 function DataStore:generateResourceObj(ops, apiId, tenantObj, cors)
@@ -60,49 +67,62 @@ function DataStore:generateResourceObj(ops, apiId, tenantObj, cors)
 end
 
 function DataStore:createResource(key, field, resourceObj)
-  return self.impl.createResource(self.ds, key, field, resourceObj)
+  return self.impl.createResource(self.ds, key, field, resourceObj, self.snapshotId)
 end
 
 function DataStore:addResourceToIndex(index, resourceKey)
-  return self.impl.addResourceToIndex(self.ds, index, resourceKey)
+  return self.impl.addResourceToIndex(self.ds, index, resourceKey, self.snapshotId)
 end
 
 function DataStore:deleteResourceFromIndex(index, resourceKey)
-  return self.impl.deleteResourceFromIndex(self.ds, index, resourceKey)
+  return self.impl.deleteResourceFromIndex(self.ds, index, resourceKey, self.snapshotId)
 end
 function DataStore:getResource(key, field)
-  return self.impl.getResource(self.ds, key, field)
+  return self.impl.getResource(self.ds, key, field, self.snapshotId)
 end
 function DataStore:getAllResources(tenantId)
-  return self.impl.getAllResources(self.ds, tenantId)
+  return self.impl.getAllResources(self.ds, tenantId, self.snapshotId)
 end
 function DataStore:deleteResource(key, field)
-  return self.impl.deleteResource(self.ds, key, field)
+  return self.impl.deleteResource(self.ds, key, field, self.snapshotId)
 end
+
 function DataStore:addTenant(id, tenantObj)
   return self.impl.addTenant(self.ds, id, tenantObj)
 end
+
 function DataStore:getAllTenants()
   return self.impl.getAllTenants(self.ds)
 end
+
 function DataStore:getTenant(id)
   return self.impl.getTenant(self.ds, id)
 end
+
 function DataStore:deleteTenant(id)
   return self.impl.deleteTenant(self.ds, id)
 end
+
 function DataStore:createSubscription(key)
-  return self.impl.createSubscription(self.ds, key)
+  return self.impl.createSubscription(self.ds, key, self.snapshotId)
 end
+
 function DataStore:deleteSubscription(key)
-  return self.impl.deleteSubscription(self.ds, key)
+  return self.impl.deleteSubscription(self.ds, key, self.snapshotId)
+end
+
+function DataStore:getSubscriptions(artifactId, tenantId)
+  return self.impl.getSubscriptions(artifactId, tenantId)
 end
+
 function DataStore:healthCheck()
   return self.impl.healthCheck(self.ds)
 end
+
 function DataStore:addSwagger(id, swagger)
   return self.impl.addSwagger(self.ds, id, swagger)
 end
+
 function DataStore:getSwagger(id)
   return self.impl.getSwagger(self.ds, id)
 end
@@ -120,7 +140,7 @@ function DataStore:saveOAuthToken(provider, token, body, ttl)
 end
 
 function DataStore:exists(key)
-  return self.impl.exists(self.ds, key)
+  return self.impl.exists(self.ds, key, self.snapshotId)
 end
 
 function DataStore:setRateLimit(key, value, interval, expires)
@@ -129,8 +149,22 @@ end
 function DataStore:getRateLimit(key)
   return self.impl.getRateLimit(self.ds, key)
 end
--- to be removed in the future
-
+-- o be removed in the future
+
+function DataStore:deleteSubscriptionAdv(artifactId, tenantId, clientId)
+  local subscriptionKey = utils.concatStrings({"subscriptions:tenant:", tenantId, ":api:", artifactId})
+  local key = utils.concatStrings({subscriptionKey, ":key:", clientId})
+  if self:exists(key) == 1 then
+    self:deleteSubscription(key)
+  else
+    local pattern = utils.concatStrings({subscriptionKey, ":clientsecret:" , clientId, ":*"})
+    local res = self.impl.cleanSubscriptions(self.ds, pattern)
+    if res == false then
+      return false
+    end
+  end
+  return true
+end
 
 function _M.init()
   return DataStore:init()
diff --git a/scripts/lua/lib/redis.lua b/scripts/lua/lib/redis.lua
index 78caab4..bfc24fc 100644
--- a/scripts/lua/lib/redis.lua
+++ b/scripts/lua/lib/redis.lua
@@ -126,15 +126,16 @@ function _M.addAPI(red, id, apiObj, existingAPI)
       end
     end
   else
+    local snapshotId = _M.getSnapshotId(red, apiObj.tenantId)
     -- Delete all resources for the existingAPI
     local basePath = existingAPI.basePath:sub(2)
     for path, v in pairs(existingAPI.resources) do
       local gatewayPath = ngx.unescape_uri(utils.concatStrings({basePath, ngx.escape_uri(path)}))
       gatewayPath = gatewayPath:sub(1,1) == "/" and gatewayPath:sub(2) or gatewayPath
       local redisKey = utils.concatStrings({"resources:", existingAPI.tenantId, ":", gatewayPath})
-      _M.deleteResource(red, redisKey, REDIS_FIELD)
+      _M.deleteResource(red, redisKey, REDIS_FIELD, snapshotId)
       local indexKey = utils.concatStrings({"resources:", existingAPI.tenantId, ":__index__"})
-      _M.deleteResourceFromIndex(red, indexKey, redisKey)
+      _M.deleteResourceFromIndex(red, indexKey, redisKey, snapshotId)
     end
   end
   -- Add new API
@@ -180,7 +181,10 @@ function _M.deleteAPI(red, id)
   end
 end
 
-function _M.resourceToApi(red, resource)
+function _M.resourceToApi(red, resource, snapshotId)
+  if snapshotId ~= nil then
+    resource = utils.concatStrings({'snapshots:', snapshotId, ':', resource})
+  end
   local resource = hget(red, resource, "resources")
   if resource == ngx.null then
     return nil
@@ -232,7 +236,10 @@ end
 -- @param key redis resource key
 -- @param field redis resource field
 -- @param resourceObj redis object containing operations for resource
-function _M.createResource(red, key, field, resourceObj)
+function _M.createResource(red, key, field, resourceObj, snapshotId)
+  if snapshotId ~= nil then
+    key = utils.concatStrings({'snapshots:', snapshotId, ':', key})
+  end
   -- Add/update resource to redis
   local ok, err = hset(red, key, field, resourceObj)
   if not ok then
@@ -244,7 +251,10 @@ end
 -- @param red redis client instance
 -- @param index index key
 -- @param resourceKey resource key to add
-function _M.addResourceToIndex(red, index, resourceKey)
+function _M.addResourceToIndex(red, index, resourceKey, snapshotId)
+  if snapshotId ~= nil then
+    index = utils.concatStrings({'snapshots:', snapshotId, ':', index})
+  end
   local ok, err = sadd(red, index, resourceKey)
   if not ok then
     request.err(500, utils.concatStrings({"Failed to update the resource index set: ", err}))
@@ -255,7 +265,10 @@ end
 -- @param red redis client instance
 -- @param index index key
 -- @param key resourceKey key to delete
-function _M.deleteResourceFromIndex(red, index, resourceKey)
+function _M.deleteResourceFromIndex(red, index, resourceKey, snapshotId)
+  if snapshotId ~= nil then
+    index = utils.concatStrings({'snapshots:', snapshotId, ':', index})
+  end
   local ok, err = srem(red, index, resourceKey)
   if not ok then
     request.err(500, utils.concatStrings({"Failed to update the resource index set: ", err}))
@@ -266,8 +279,12 @@ end
 -- @param red redis client instance
 -- @param key redis resource key
 -- @param field redis resource field
+-- @param snapshotId an optional snapshotId
 -- @return resourceObj redis object containing operations for resource
-function _M.getResource(red, key, field)
+function _M.getResource(red, key, field, snapshotId)
+  if snapshotId ~= nil then
+    key = utils.concatStrings({"snapshots:", snapshotId, ":", key})
+  end
   local resourceObj, err = hget(red, key, field)
   if not resourceObj then
     request.err(500, utils.concatStrings({"Failed to retrieve the resource: ", err}))
@@ -282,19 +299,31 @@ end
 --- Get all resource keys for a tenant in redis
 -- @param red redis client instance
 -- @param tenantId tenant id
-function _M.getAllResources(red, tenantId)
-  local keys, err = smembers(red, utils.concatStrings({"resources:", tenantId, ":__index__"}))
+function _M.getAllResources(red, tenantId, snapshotId)
+  local key = utils.concatStrings({'resources:', tenantId, ':__index__'})
+  if snapshotId ~= nil then
+    key = utils.concatStrings({'snapshots:', snapshotId, ':', key})
+  end
+  local keys, err = smembers(red, key)
   if not keys then
     request.err(500, utils.concatStrings({"Failed to retrieve resource keys: ", err}))
   end
-  return keys
+  local result = {}
+  for _, v in ipairs(keys) do
+    local str = v:gsub(utils.concatStrings({'snapshots:', snapshotId, ':', ''}), '')
+    table.insert(result, str)
+  end
+  return result
 end
 
 --- Delete resource in redis
 -- @param red redis client instance
 -- @param key redis resource key
 -- @param field redis resource field
-function _M.deleteResource(red, key, field)
+function _M.deleteResource(red, key, field, snapshotId)
+  if snapshotId ~= nil then
+    key = utils.concatStrings({'snapshots:', snapshotId, ':', key})
+  end
   local resourceObj, err = hget(red, key, field)
   if not resourceObj then
     request.err(500, utils.concatStrings({"Failed to delete the resource: ", err}))
@@ -339,6 +368,14 @@ function _M.addTenant(red, id, tenantObj)
   return tenantObj
 end
 
+function _M.getSnapshotId(red, tenantId)
+ local result = red:get(utils.concatStrings({'snapshots:tenant:', tenantId}))
+  if result == ngx.null then
+    return nil
+  end
+  return result
+end
+
 --- Get all tenants from redis
 -- @param red Redis client instance
 function _M.getAllTenants(red)
@@ -380,7 +417,10 @@ end
 --- Create/update subscription/apikey in redis
 -- @param red redis client instance
 -- @param key redis subscription key to create
-function _M.createSubscription(red, key)
+function _M.createSubscription(red, key, snapshotId)
+  if snapshotId ~= nil then
+    key = utils.concatStrings({'snapshots:', snapshotId, ':', key})
+  end
   -- Add/update a subscription key to redis
   local ok, err = set(red, key, '')
   if not ok then
@@ -391,7 +431,10 @@ end
 --- Delete subscription/apikey int redis
 -- @param red redis client instance
 -- @param key redis subscription key to delete
-function _M.deleteSubscription(red, key)
+function _M.deleteSubscription(red, key, snapshotId)
+  if snapshotId ~= nil then
+    key = utils.concatStrings({'snapshots:', snapshotId, ':', key})
+  end
   local subscription, err = get(red, key)
   if not subscription then
     request.err(500, utils.concatStrings({"Failed to delete the subscription key: ", err}))
@@ -405,6 +448,30 @@ function _M.deleteSubscription(red, key)
   end
 end
 
+function _M.cleanSubscriptions(red, pattern)
+  return red:eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))", 0, pattern)
+end
+
+
+function _M.getSubscriptions(red, artifactId, tenantId, snapshotId)
+  local res = red:scan(0, "match", utils.concatStrings({"subscriptions:tenant:", tenantId, ":api:", artifactId, ":*"}))
+  local cursor = res[1]
+  local subscriptions = {}
+  for _, v in pairs(res[2]) do
+    local matched = {string.match(v, "subscriptions:tenant:([^:]+):api:([^:]+):([^:]+):([^:]+):*")}
+    subscriptions[#subscriptions + 1] = matched[4]
+  end
+  while cursor ~= "0" do
+    res = red:scan(cursor, "match", utils.concatStrings({"subscriptions:tenant:", tenantId, ":api:", artifactId, ":*"}))
+    cursor = res[1]
+    for _, v in pairs(res[2]) do
+      local matched = {string.match(v, "subscriptions:tenant:([^:]+):api:([^:]+):([^:]+):([^:]+):*")}
+      subscriptions[#subscriptions + 1] = matched[4]
+    end
+  end
+  return subscriptions
+end
+
 -----------------------------
 --- OAuth Tokens          ---
 -----------------------------
@@ -470,9 +537,17 @@ end
 function _M.getRateLimit(red, key)
   return get(red, key)
 end
+
+function _M.lockSnapshot(red, snapshotId)
+  red:set(utils.concatStrings({'lock:snapshots:', snapshotId}), 'true')
+  red:expire(utils.concatStrings({'lock:snapshots:', snapshotId}), 60)
+end
 -- LRU Caching methods
 
-function exists(red, key)
+function exists(red, key, snapshotId)
+  if snapshotId ~= nil then
+    key = utils.concatStrings({'snapshots:', snapshotId, ':', key})
+  end
   if CACHING_ENABLED then
     local cached = c:get(key)
     if cached ~= nil then
diff --git a/scripts/lua/management/lib/resources.lua b/scripts/lua/management/lib/resources.lua
index 041c481..5c530cb 100644
--- a/scripts/lua/management/lib/resources.lua
+++ b/scripts/lua/management/lib/resources.lua
@@ -38,6 +38,8 @@ function _M.addResource(dataStore, resource, gatewayPath, tenantObj)
   local apiId = resource.apiId
   local cors = resource.cors
   local resourceObj = dataStore:generateResourceObj(operations, apiId, tenantObj, cors)
+  print ('setting snapshot for id: ' .. tenantObj.id)
+  dataStore:setSnapshotId(tenantObj.id)
   dataStore:createResource(redisKey, REDIS_FIELD, resourceObj)
   local indexKey = utils.concatStrings({"resources:", tenantObj.id, ":__index__"})
   dataStore:addResourceToIndex(indexKey, redisKey)
@@ -48,6 +50,7 @@ end
 -- @param gatewayPath path in gateway
 -- @param tenantId tenant id
 function _M.deleteResource(dataStore, gatewayPath, tenantId)
+  dataStore:setSnapshotId(tenantId)
   local redisKey = utils.concatStrings({"resources:", tenantId, ":", gatewayPath})
   dataStore:deleteResource(redisKey, REDIS_FIELD)
   local indexKey = utils.concatStrings({"resources:", tenantId, ":__index__"})
diff --git a/scripts/lua/management/lib/subscriptions.lua b/scripts/lua/management/lib/subscriptions.lua
index 9671ef1..fdbde79 100644
--- a/scripts/lua/management/lib/subscriptions.lua
+++ b/scripts/lua/management/lib/subscriptions.lua
@@ -26,8 +26,9 @@ local utils = require "lib/utils"
 
 local _M = {}
 
-function _M.addSubscription(red, artifactId, tenantId, clientId, clientSecret, hashFunction)
+function _M.addSubscription(dataStore, artifactId, tenantId, clientId, clientSecret, hashFunction)
   local subscriptionKey = utils.concatStrings({"subscriptions:tenant:", tenantId, ":api:", artifactId})
+  dataStore:setSnapshotId(tenantId)
   if clientSecret ~= nil then
     if hashFunction == nil then
       hashFunction = utils.hash
@@ -36,41 +37,16 @@ function _M.addSubscription(red, artifactId, tenantId, clientId, clientSecret, h
   else
     subscriptionKey = utils.concatStrings({subscriptionKey, ":key:", clientId})
   end
-  redis.createSubscription(red, subscriptionKey)
+  dataStore:createSubscription(subscriptionKey)
 end
 
-function _M.getSubscriptions(red, artifactId, tenantId)
-  local res = red:scan(0, "match", utils.concatStrings({"subscriptions:tenant:", tenantId, ":api:", artifactId, ":*"}))
-  local cursor = res[1]
-  local subscriptions = {}
-  for _, v in pairs(res[2]) do
-    local matched = {string.match(v, "subscriptions:tenant:([^:]+):api:([^:]+):([^:]+):([^:]+):*")}
-    subscriptions[#subscriptions + 1] = matched[4]
-  end
-  while cursor ~= "0" do
-    res = red:scan(cursor, "match", utils.concatStrings({"subscriptions:tenant:", tenantId, ":api:", artifactId, ":*"}))
-    cursor = res[1]
-    for _, v in pairs(res[2]) do
-      local matched = {string.match(v, "subscriptions:tenant:([^:]+):api:([^:]+):([^:]+):([^:]+):*")}
-      subscriptions[#subscriptions + 1] = matched[4]
-    end
-  end
-  return subscriptions
+function _M.getSubscriptions(dataStore, artifactId, tenantId)
+  dataStore:setSnapshotId(tenantId)
+  return dataStore:getSubscriptions(artifactId, tenantId)
 end
 
-function _M.deleteSubscription(red, artifactId, tenantId, clientId)
-  local subscriptionKey = utils.concatStrings({"subscriptions:tenant:", tenantId, ":api:", artifactId})
-  local key = utils.concatStrings({subscriptionKey, ":key:", clientId})
-  if redis.exists(red, key) == 1 then
-    redis.deleteSubscription(red, key)
-  else
-    local pattern = utils.concatStrings({subscriptionKey, ":clientsecret:" , clientId, ":*"})
-    local res = red:eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))", 0, pattern)
-    if res == false then
-      return false
-    end
-  end
-  return true
+function _M.deleteSubscription(dataStore, artifactId, tenantId, clientId)
+  return dataStore:deleteSubscriptionAdv(artifactId, tenantId, clientId)
 end
 
-return _M
\ No newline at end of file
+return _M
diff --git a/scripts/lua/management/routes/apis.lua b/scripts/lua/management/routes/apis.lua
index 5e70e4f..6e9d3d1 100644
--- a/scripts/lua/management/routes/apis.lua
+++ b/scripts/lua/management/routes/apis.lua
@@ -99,7 +99,7 @@ function getAPIs(dataStore)
           managed_url = api.managedUrl,
           open_api_doc = dataStore:getSwagger(api.id)
         }
-        dataStore.close()
+        dataStore:close()
         request.success(200, cjson.encode(returnObj))
       end
     end
@@ -134,7 +134,7 @@ function addAPI(dataStore)
   end
   -- Check for api id in JSON body
   if existingAPI == nil and decoded.id ~= nil then
-    existingAPI = dataStore.getAPI(decoded.id)
+    existingAPI = dataStore:getAPI(decoded.id)
     if existingAPI == nil then
       dataStore:close()
       request.err(404, utils.concatStrings({"Unknown API id ", decoded.id}))
diff --git a/scripts/lua/management/routes/subscriptions.lua b/scripts/lua/management/routes/subscriptions.lua
index 40b5986..0d2c5e5 100644
--- a/scripts/lua/management/routes/subscriptions.lua
+++ b/scripts/lua/management/routes/subscriptions.lua
@@ -33,12 +33,12 @@ local REDIS_PASS = os.getenv("REDIS_PASS")
 
 local _M = {}
 
-function _M.requestHandler()
+function _M.requestHandler(dataStore)
   local version = ngx.var.version
   if version == "v2" then
-    v2()
+    v2(dataStore)
   elseif version == "v1" then
-    v1()
+    v1(dataStore)
   else
     request.err(404, "404 Not found")
   end
@@ -47,23 +47,25 @@ end
 
 -- v2 --
 
-function v2()
+function v2(dataStore)
   local requestMethod = ngx.req.get_method()
   if requestMethod == "POST" or requestMethod == "PUT" then
-    v2AddSubscription()
+    v2AddSubscription(dataStore)
   elseif requestMethod == "GET" then
-    v2GetSubscriptions()
+    v2GetSubscriptions(dataStore)
   elseif requestMethod == "DELETE" then
-    v2DeleteSubscription()
+    v2DeleteSubscription(dataStore)
   else
+    dataStore:close()
     request.err(400, "Invalid verb")
   end
 end
 
-function v2AddSubscription()
+function v2AddSubscription(dataStore)
   ngx.req.read_body()
   local args = ngx.req.get_body_data()
   if not args then
+    dataStore:close()
     request.err(400, "Missing request body.")
   end
   local decoded = cjson.decode(args)
@@ -71,13 +73,12 @@ function v2AddSubscription()
   if res == false then
     request.err(err.statusCode, err.message)
   end
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
   local artifactId = decoded.artifact_id
   local tenantId = ngx.var.tenant_id
   local clientId = decoded.client_id
   local clientSecret = decoded.client_secret
-  subscriptions.addSubscription(red, artifactId, tenantId, clientId, clientSecret, utils.hash)
-  redis.close(red)
+  subscriptions.addSubscription(dataStore, artifactId, tenantId, clientId, clientSecret, utils.hash)
+  dataStore:close()
   local result = {
     message = utils.concatStrings({"Subscription '", clientId, "' created for API '", artifactId, "'"})
   }
@@ -85,20 +86,19 @@ function v2AddSubscription()
   request.success(200, cjson.encode(result))
 end
 
-function v2GetSubscriptions()
+function v2GetSubscriptions(dataStore)
   local tenantId = ngx.var.tenant_id
   local artifactId = ngx.req.get_uri_args()["artifact_id"]
   if artifactId == nil or artifactId == "" then
     request.err(400, "Missing artifact_id")
   end
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
-  local subscriptionList = subscriptions.getSubscriptions(red, artifactId, tenantId)
+  local subscriptionList = subscriptions.getSubscriptions(dataStore, artifactId, tenantId)
   redis.close(red)
   ngx.header.content_type = "application/json; charset=utf-8"
   request.success(200, cjson.encode(subscriptionList))
 end
 
-function v2DeleteSubscription()
+function v2DeleteSubscription(dataStore)
   local clientId = ngx.var.client_id
   local tenantId = ngx.var.tenant_id
   local artifactId = ngx.req.get_uri_args()["artifact_id"]
@@ -108,8 +108,7 @@ function v2DeleteSubscription()
   if artifactId == nil or artifactId == "" then
     request.err(400, "Missing artifact_id")
   end
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
-  local res = subscriptions.deleteSubscription(red, artifactId, tenantId, clientId)
+  local res = subscriptions.deleteSubscription(dataStore, artifactId, tenantId, clientId)
   if res == false then
     request.err(404, "Subscription doesn't exist")
   end
@@ -120,38 +119,38 @@ end
 
 -- v1 --
 
-function v1()
+function v1(dataStore)
   local requestMethod = ngx.req.get_method()
   if requestMethod == "POST" or requestMethod == "PUT" then
-    addSubscription()
+    addSubscription(dataStore)
   elseif requestMethod == "DELETE" then
-    deleteSubscription()
+    deleteSubscription(dataStore)
   else
+    dataStore:close()
     request.err(400, "Invalid verb")
   end
 end
 
-function addSubscription()
-  local redisKey = validateSubscriptionBody()
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
-  redis.createSubscription(red, redisKey)
-  redis.close(red)
+function addSubscription(dataStore)
+  local redisKey = validateSubscriptionBody(dataStore)
+  dataStore:createSubscription(redisKey)
+  dataStore:close()
   request.success(200, "Subscription created.")
 end
 
-function deleteSubscription()
-  local redisKey = validateSubscriptionBody()
-  local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
-  redis.deleteSubscription(red, redisKey)
-  redis.close(red)
+function deleteSubscription(dataStore)
+  local redisKey = validateSubscriptionBody(dataStore)
+  dataStore:deleteSubscription(redisKey)
+  dataStore:close()
   request.success(200, "Subscription deleted.")
 end
 
-function validateSubscriptionBody()
+function validateSubscriptionBody(dataStore)
   -- Read in the PUT JSON Body
   ngx.req.read_body()
   local args = ngx.req.get_body_data()
   if not args then
+    dataStore:close()
     request.err(400, "Missing request body.")
   end
   -- Convert json into Lua table
@@ -159,12 +158,14 @@ function validateSubscriptionBody()
   -- Check required fields
   local res, err = utils.tableContainsAll(decoded, {"key", "scope", "tenantId"})
   if res == false then
+    dataStore:close()
     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
+  dataStore:setSnapshotId(decoded.tenantId)
   local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenantId})
   if decoded.scope == "tenant" then
     redisKey = prefix
@@ -172,15 +173,18 @@ function validateSubscriptionBody()
     if resource ~= nil then
       redisKey = utils.concatStrings({prefix, ":resource:", resource})
     else
+      dataStore:close()
       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
+      dataStore:close()
       request.err(400, "\"apiId\" missing from request body.")
     end
   else
+    dataStore:close()
     request.err(400, "Invalid scope")
   end
   redisKey = utils.concatStrings({redisKey, ":key:", decoded.key})
diff --git a/scripts/lua/routing.lua b/scripts/lua/routing.lua
index cd1d11b..09003c0 100644
--- a/scripts/lua/routing.lua
+++ b/scripts/lua/routing.lua
@@ -33,12 +33,18 @@ local backendRouting = require "policies/backendRouting"
 local cors = require "cors"
 
 
+local SNAPSHOTTING = os.getenv('SNAPSHOTTING')
+
 local _M = {}
 
 --- Main function that handles parsing of invocation details and carries out implementation
 function _M.processCall(dataStore)
   -- Get resource object from redis
   local tenantId = ngx.var.tenant
+
+  if SNAPSHOTTING == 'true' then
+    dataStore:setSnapshotId(tenantId) 
+  end
   local gatewayPath = ngx.var.gatewayPath
   local i, j = ngx.var.request_uri:find("/api/([^/]+)")
   ngx.var.analyticsUri = ngx.var.request_uri:sub(j+1)
@@ -46,11 +52,17 @@ function _M.processCall(dataStore)
     setRequestLogs()
   end
   local resourceKeys = dataStore:getAllResources(tenantId)
+  print(cjson.encode(resourceKeys))
   local redisKey = _M.findResource(resourceKeys, tenantId, gatewayPath)
   if redisKey == nil then
     request.err(404, 'Not found.')
   end
-  local obj = cjson.decode(dataStore:getResource(redisKey, "resources"))
+  local resource = dataStore:getResource(redisKey, "resources")
+  if resource == nil then
+    request.err(404, 'Snapshot not found.')
+  end
+  local obj = cjson.decode(resource) 
+
   cors.processCall(obj)
   ngx.var.tenantNamespace = obj.tenantNamespace
   ngx.var.tenantInstance = obj.tenantInstance
@@ -82,7 +94,7 @@ function _M.processCall(dataStore)
         setRequestLogs()
       end
       dataStore:close()
-      return
+      return nil
     end
   end
   request.err(404, 'Whoops. Verb not supported.')
diff --git a/tests/fakeredis.lua b/tests/fakeredis.lua
index 1e9fddc..afad80f 100644
--- a/tests/fakeredis.lua
+++ b/tests/fakeredis.lua
@@ -1,5 +1,5 @@
 local unpack = table.unpack or unpack
-
+local cjson = require 'cjson'
 --- Bit operations
 
 local bit
@@ -308,6 +308,11 @@ local bitcount = function(self, k, i1, i2)
     return r
 end
 
+
+local expire = function(self, k, x)
+  return nil -- do nothing for now
+end
+
 local bitop = function(self, op, k, ...)
     assert(type(op) == "string")
     op = op:lower()
@@ -1597,6 +1602,7 @@ local methods = {
   randomkey = randomkey, -- () -> [k|nil]
   rename = chkargs_wrap(rename, 2), -- (k,k2) -> true
   renamenx = chkargs_wrap(renamenx, 2), -- (k,k2) -> ! existed? k2
+  expire = chkargs_wrap(expire, 2),
   -- strings
   append = chkargs_wrap(append, 2), -- (k,v) -> #new
   bitcount = bitcount, -- (k,[start,end]) -> n
@@ -1613,7 +1619,7 @@ local methods = {
   mget = mget, -- (k1,...) -> {v1,...}
   mset = mset, -- (k1,v1,...) -> true
   msetnx = msetnx, -- (k1,v1,...) -> worked? (i.e. !existed? any k)
-  set = chkargs_wrap(set, 2), -- (k,v) -> true
+  set = chkargs_wrap(set, 2), -- (k,v,expiration) -> true -- toss the expiration if it's passed in
   setbit = setbit, -- (k,offset,b) -> old
   setnx = chkargs_wrap(setnx, 2), -- (k,v) -> worked? (i.e. !existed?)
   setrange = setrange, -- (k,offset,val) -> #new
diff --git a/tests/scripts/lua/lib/subscriptions.lua b/tests/scripts/lua/lib/subscriptions.lua
index 916d0ac..250733d 100644
--- a/tests/scripts/lua/lib/subscriptions.lua
+++ b/tests/scripts/lua/lib/subscriptions.lua
@@ -35,13 +35,15 @@ describe("Testing v2 subscriptions API", function()
     local artifactId = "abc"
     local tenantId = "testtenant"
     local clientId = "xxx"
-    subscriptions.addSubscription(red, artifactId, tenantId, clientId)
+    local ds = require 'lib/dataStore'
+    local dataStore = ds.initWithDriver(red)
+    subscriptions.addSubscription(dataStore, artifactId, tenantId, clientId)
     local generated = red:exists("subscriptions:tenant:" .. tenantId .. ":api:" .. artifactId .. ":key:" .. clientId)
     assert.are.equal(1, generated)
     -- client id and secret
     local newClientId = "newclientid"
     local clientSecret = "secret"
-    subscriptions.addSubscription(red, artifactId, tenantId, newClientId, clientSecret, generateHash)
+    subscriptions.addSubscription(dataStore, artifactId, tenantId, newClientId, clientSecret, generateHash)
     generated = red:exists("subscriptions:tenant:" .. tenantId .. ":api:" .. artifactId .. ":clientsecret:" .. newClientId .. ":" .. generateHash(clientSecret))
     assert.are.equal(1, generated)
     -- wrong secret
@@ -49,15 +51,17 @@ describe("Testing v2 subscriptions API", function()
     generated = red:exists("subscriptions:tenant:" .. tenantId .. ":api:" .. artifactId .. ":clientsecret:" .. newClientId .. ":" .. generateHash(badsecret))
     assert.are.equal(0, generated)
   end)
-  
+
   it("should delete a subscription", function()
     local artifactId = "abc"
     local tenantId = "testtenant"
     local clientId = "12345"
-    subscriptions.addSubscription(red, artifactId, tenantId, clientId)
+    local ds = require 'lib/dataStore'
+    local dataStore = ds.initWithDriver(red)
+    subscriptions.addSubscription(dataStore, artifactId, tenantId, clientId)
     local generated = red:exists("subscriptions:tenant:" .. tenantId .. ":api:" .. artifactId .. ":key:" .. clientId)
     assert.are.same(1, generated)
-    subscriptions.deleteSubscription(red, artifactId, tenantId, clientId)
+    subscriptions.deleteSubscription(dataStore, artifactId, tenantId, clientId)
     generated = red:exists("subscriptions:tenant:" .. tenantId .. ":api:" .. artifactId .. ":key:" .. clientId)
     assert.are.same(0, generated)
   end)
@@ -66,4 +70,4 @@ end)
 -- Generate fake hash
 function generateHash(str)
   return string.byte(str)*13 + 2961
-end
\ No newline at end of file
+end
diff --git a/tests/scripts/lua/routing.lua b/tests/scripts/lua/routing.lua
index 8d5b68b..2447d51 100644
--- a/tests/scripts/lua/routing.lua
+++ b/tests/scripts/lua/routing.lua
@@ -58,6 +58,7 @@ describe('Testing routing module', function()
     local tenant = 'guest'
     local path = 'bp1/bad/path'
     local actual = routing.findResource(keys, tenant, path)
+    print(actual)
     assert.are.same(expected, actual)
   end)
 
@@ -118,5 +119,20 @@ describe('Testing routing module', function()
     actual = ngx.ctx.var2
     assert.are.same(expected, actual)
   end)
+end)
 
+describe('Testing routing with snapshotting', function()
+  it('Gets a redis key for a tenant with the correct snapshot', function()
+    local red = fakeredis.new()
+    red:set('snapshots:tenant:test', 'abcd1234')
+    red:sadd('snapshots:abcd1234:resources:test:__index__', 'snapshots:abcd1234:resources:test:v1/test')
+    red:hset('snapshots:abcd1234:resources:test:v1/test', 'resources', '{"operations":{}}')
+    local ds = require 'lib/dataStore'
+    local dataStore = ds.initWithDriver(red)
+    dataStore:setSnapshotId('test')
+    local result = dataStore:getAllResources('test')[1]
+    assert.are.same(result, 'resources:test:v1/test')
+    local routing = require  'routing'
+    local result = routing.findResource(dataStore:getAllResources('test'), 'test', 'v1/test')
+  end)
 end)
diff --git a/tests/scripts/lua/security.lua b/tests/scripts/lua/security.lua
index 239650d..58d3f4e 100644
--- a/tests/scripts/lua/security.lua
+++ b/tests/scripts/lua/security.lua
@@ -47,8 +47,12 @@ describe('API Key module', function()
         "type":"apikey"
       }
     ]])
-    red:hset('resources:abcd:v1/test', 'resources', '{"apiId":"bnez"}')
-    red:set('subscriptions:tenant:abcd:api:bnez:key:a1234', 'true')
+    red:set('snapshots:tenant:abcd', 'abcdefg') 
+
+    red:hset('snapshots:abcdefg:resources:abcd:v1/test', 'resources', '{"apiId":"bnez"}')
+    red:set('snapshots:abcdefg:subscriptions:tenant:abcd:api:bnez:key:a1234', 'true')
+    local dataStore = ds.initWithDriver(red) 
+    dataStore:setSnapshotId('abcd')
     local key = apikey.process(dataStore, securityObj, function() return "fakehash" end)
     assert.same(key, 'a1234')
   end)
@@ -319,6 +323,62 @@ describe('Client Secret Module', function()
     local result = clientSecret.processWithHashFunction(red, cjson.decode(securityObj), function() return "fakehash" end)
     assert(result)
   end)
+  it('Validates a client secret pair with default names and snapshotting', function()
+    local ngx = fakengx.new()
+    local red = fakeredis.new()
+    local ngxattrs = [[
+      {
+       "http_X_Client_ID":"abcd",
+       "http_X_Client_Secret":"1234",
+       "tenant":"1234",
+       "gatewayPath":"v1/test"
+      }
+    ]]
+    ngx.req = { get_uri_args = function() return {} end }
+    ngx.var = cjson.decode(ngxattrs)
+    _G.ngx = ngx
+    local securityObj = [[
+      {
+        "type":"clientsecret",
+        "scope":"resource"
+      }
+    ]]
+    red:set("snapshots:tenant:1234", "abcdefg")
+    red:set("snapshots:abcdefg:subscriptions:tenant:1234:resource:v1/test:clientsecret:abcd:fakehash", "true")
+    local ds = require 'lib/dataStore'
+    local dataStore = ds.initWithDriver(red) 
+    dataStore:setSnapshotId("1234")
+    local result = clientSecret.processWithHashFunction(dataStore, cjson.decode(securityObj), function() return "fakehash" end)
+    assert(result)
+  end)
+  it('Doesn\'t validate a client secret pair in a different snapshot', function()
+    local ngx = fakengx.new()
+    local red = fakeredis.new()
+    local ngxattrs = [[
+      {
+       "http_X_Client_ID":"abcd",
+       "http_X_Client_Secret":"1234",
+       "tenant":"1234",
+       "gatewayPath":"v1/test"
+      }
+    ]]
+    ngx.req = { get_uri_args = function() return {} end }
+    ngx.var = cjson.decode(ngxattrs)
+    _G.ngx = ngx
+    local securityObj = [[
+      {
+        "type":"clientsecret",
+        "scope":"resource"
+      }
+    ]]
+    red:set("snapshots:tenant:1234", "abcdefg")
+    red:set("snapshots:abcdefh:subscriptions:tenant:1234:resource:v1/test:clientsecret:abcd:fakehash", "true")
+    local ds = require 'lib/dataStore'
+    local dataStore = ds.initWithDriver(red) 
+    dataStore:setSnapshotId("1234")
+    local result = clientSecret.processWithHashFunction(dataStore, cjson.decode(securityObj), function() return "fakehash" end)
+    assert.falsy(result)
+  end)
   it('Validates a client secret pair with new names', function()
     local ngx = fakengx.new()
     local red = fakeredis.new()

-- 
To stop receiving notification emails like this one, please contact
['"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>'].