kong/kong/plugins/oidc/resty/session/storage/sentinel.lua (224 lines of code) (raw):

local setmetatable = setmetatable local tonumber = tonumber local concat = table.concat local sleep = ngx.sleep local null = ngx.null local var = ngx.var local UNLOCK = [[ if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end ]] local function enabled(value) if value == nil then return nil end return value == true or (value == "1" or value == "true" or value == "on") end local function ifnil(value, default) if value == nil then return default end return enabled(value) end local storage = {} storage.__index = storage function storage.new(session) ngx.log(ngx.DEBUG, "storage:new()") local config = session.sentinel local self = { prefix = config.prefix or "sessions", uselocking = ifnil(config.uselocking, true), spinlockwait = tonumber(config.spinlockwait, 10) or 150, maxlockwait = tonumber(config.maxlockwait, 10) or 30, connect_timeout = tonumber(config.connect_timeout, 10) or 50, send_timeout = tonumber(config.send_timeout, 10) or 5000, read_timeout = tonumber(config.read_timeout, 10) or 5000, keepalive_timeout = tonumber(config.keepalive_timeout, 10) or 30000, sentinel_host = config.sentinel_host or "127.0.0.1", sentinel_port = tonumber(config.sentinel_port, 10) or 26379, redis_auth_user = os.getenv("REDIS_AUTH_USER") or config.redis_auth_user or "", redis_auth_secret = os.getenv("REDIS_AUTH_SECRET") or config.redis_auth_secret or "", sentinel_master_name = config.sentinel_master_name or "mymaster", sentinel_role = config.sentinel_role or "m", sentinel_db = config.sentinel_db or "sessions", sentinel_config = config, redis = nil } self.sc = require("resty.redis.connector").new({ connect_timeout = self.connect_timeout, send_timeout = self.send_timeout, read_timeout = self.read_timeout, keepalive_timeout = self.keepalive_timeout}) return setmetatable(self, storage) end function storage:connect() ngx.log(ngx.DEBUG, "storage:connect(), host:" .. self.sentinel_host .. ":" .. self.sentinel_port) local sentinel, err = self.sc:connect({ url = "sentinel://" .. self.redis_auth_user .. ":" .. self.redis_auth_secret .. "@" .. self.sentinel_master_name .. ":" .. self.sentinel_role .. "/" .. self.sentinel_db, sentinels = { { host = self.sentinel_host, port = self.sentinel_port } } }) if not sentinel then ngx.log(ngx.ERR, "Failed to connect to Sentinel [" .. self.sentinel_host .. ":" .. self.sentinel_port .. "]:" .. err) return nil, err end ngx.log(ngx.DEBUG, "storage:connect() succeeded") self.redis = sentinel return sentinel, err end function storage:set_keepalive() ngx.log(ngx.DEBUG, "storage:set_keepalive()") return self.redis:set_keepalive(self.pool_timeout) end function storage:key(id) return concat({ self.prefix, id }, ":" ) end function storage:lock(key) ngx.log(ngx.DEBUG, "storage:lock(), key:" .. key) if not self.uselocking or self.locked then return true end if not self.token then self.token = var.request_id end local lock_key = concat({ key, "lock" }, "." ) local lock_ttl = self.maxlockwait + 1 local attempts = (1000 / self.spinlockwait) * self.maxlockwait local waittime = self.spinlockwait / 1000 for _ = 1, attempts do local ok = self.redis:set(lock_key, self.token, "EX", lock_ttl, "NX") if ok ~= null then self.locked = true return true end sleep(waittime) end return false, "unable to acquire a session lock" end function storage:unlock(key) ngx.log(ngx.DEBUG, "storage:unlock(), key:" .. key) if not self.uselocking or not self.locked then return end local lock_key = concat({ key, "lock" }, "." ) self.redis:eval(UNLOCK, 1, lock_key, self.token) self.locked = nil end function storage:get(key) ngx.log(ngx.DEBUG, "storage:get(), key:" .. key) local data, err = self.redis:get(key) if not data then ngx.log(ngx.WARN, "storage:get(), no data for key: " .. key) return nil, err end if data == null then ngx.log(ngx.WARN, "storage:get(), nil data for key: " .. key) return nil end return data end function storage:set(key, data, lifetime) ngx.log(ngx.DEBUG, "storage:set(), key:" .. key) return self.redis:setex(key, lifetime, data) end function storage:expire(key, lifetime) ngx.log(ngx.DEBUG, "storage:expire(), key:" .. key) return self.redis:expire(key, lifetime) end function storage:delete(key) ngx.log(ngx.DEBUG, "storage:delete(), key:" .. key) return self.redis:del(key) end function storage:open(id, keep_lock) ngx.log(ngx.DEBUG, "storage:open(), id:" .. id) local ok, err = self:connect() if not ok then return nil, err end local key = self:key(id) ok, err = self:lock(key) if not ok then self:set_keepalive() return nil, err end local data data, err = self:get(key) if err or not data or not keep_lock then self:unlock(key) end self:set_keepalive() return data, err end function storage:start(id) ngx.log(ngx.DEBUG, "storage:start(), id:" .. id) if not self.uselocking or not self.locked then return true end local ok, err = self:connect() if not ok then return nil, err end ok, err = self:lock(self:key(id)) self:set_keepalive() return ok, err end function storage:save(id, ttl, data, close) ngx.log(ngx.DEBUG, "storage:save(), id:" .. id) local ok, err = self:connect() if not ok then return nil, err end local key = self:key(id) ok, err = self:set(key, data, ttl) if close then self:unlock(key) end self:set_keepalive() if not ok then return nil, err end return true end function storage:close(id) ngx.log(ngx.DEBUG, "storage:close(), id:" .. id) if not self.uselocking or not self.locked then return true end local ok, err = self:connect() if not ok then return nil, err end local key = self:key(id) self:unlock(key) self:set_keepalive() return true end function storage:destroy(id) ngx.log(ngx.DEBUG, "storage:destroy(), id:" .. id) local ok, err = self:connect() if not ok then return nil, err end local key = self:key(id) ok, err = self:delete(key) self:unlock(key) self:set_keepalive() return ok, err end function storage:ttl(id, ttl, close) ngx.log(ngx.DEBUG, "storage:ttl(), id:" .. id .. " ttl:" .. ttl) local ok, err = self:connect() if not ok then return nil, err end local key = self:key(id) ok, err = self:expire(key, ttl) if close then self:unlock(key) end self:set_keepalive() return ok, err end return storage