Sindbad~EG File Manager

Current Path : /opt/nginxhttpd_/etc/openresty_config/lua/lib/
Upload File :
Current File : //opt/nginxhttpd_/etc/openresty_config/lua/lib/o2switch_redis.lua

--[[
    This file contains a modules with some functions that will help us with Redis (getting/setting key to Redis).
--]]

local _M = {}

local config = require "lib/o2switch_config"
local o2debug = require "lib/o2switch_debug"
local redis = require "resty.redis"
local json = require "cjson"
local ngx_sleep = ngx.sleep
local pcall = pcall
local type = type
local pairs = pairs
local tostring = tostring
local table_unpack = table.unpack
local ngx_null = ngx.null

-- Enum helper to avoid dealing with indexes for the data that came back from redis in an HSET
_M.enum = {
    ['proxyPassProtocol'] = 1,
    ['proxyPassIp'] = 2,
    ['proxyPassPort'] = 3,
    ['listenToIp'] = 4,
    ['proxyPassSslProtocol'] = 5,
    ['proxyPassSslIp'] = 6,
    ['proxyPassSslPort'] = 7,
    -- ['listenToIp'] = 8,
    ['originalBackendIp'] = 9,
    ['additionalsParams'] = 10
}

--- A retryable redis getter (hget, hmget etc... in the callback)
-- @callback function The redis callback function to call. So a redis:hmget, redis:hget for instance. The callback must have a return signature off : data, nil|err
-- @return result, nil|string     
local function retryableRedisGetter(callback, ...)
    --o2debug.debug('retryableRedisGetter called')
    local arg={...}
    local data, err = nil, nil

    for i=1, (config.redisRetryCount + 1) do
        data, err = callback(table_unpack(arg))
        if err == nil and data ~= ngx_null then
            return data, err
        end
        o2debug.error('retryableRedisGetter ' .. tostring(i) .. ' : ' .. tostring(err) .. ' : ' ..  type(data))        
        ngx_sleep(config.redisSleepBetweenTries * i)
    end

    return data, err
end

--- Return a redis active instance/connection (low level function)
-- @masterRedis bool True to retrieve the master redis instance (write)
-- @return RedisConnection|nil, ErrorMessage|nil
function _M.getRedisInstance(masterRedis)
    local masterRedis = masterRedis == true

    local red, err = redis:new()
    if not red then
        o2debug.critical("Failed to create a redis instance : " .. (err or 'no err msg'))
        return nil, "Failed to create a redis instance : " .. (err or 'no err msg')
    end

    red:set_timeout(config.redisTimeout or 500)

    local ok, res, err = nil, nil, nil

    for k, conf in pairs(config.redisConfig) do
        -- If we want a master redis, we'll ignore the slave configuration (not writable)
        if masterRedis and not conf['master'] then
            goto continue
        end

        ok, err = red:connect(conf['host'], conf['port'])
        -- --local times, err = red:get_reused_times()
        -- --o2debug.error("get_reused_times:" .. tostring(times) .. ' ' .. tostring(err))

        -- It's ok and we dont need to authenticate, exit the loop now
        if ok and not conf['pass'] and err == nil then
            break
        end

        -- It's ok and we need to authenticate, authenticate and then exit the loop if authenticated properly
        if ok and conf['pass'] then
            res, err = red:auth(conf['pass'])
            if res and err == nil then
                break
            end
            o2debug.critical("Wrong redis password ? " .. conf['host'] .. " : "  ..  conf['pass'] or 'n/a' .. '/' .. (err or 'no err msg'))
        end

        o2debug.critical("Failed to connect to a Redis server " .. conf['host'] .. " : " .. (err or 'no err msg'))

        -- If we are here, something has gone bad so we must iterate to the next loop
        ::continue::
    end

    -- We must check if we have a valid redis connection
    if not ok or err ~= nil then
        o2debug.critical("Failed to connect to ALL redis available")
        return nil, "Failed to connect to ALL redis available"
    end

    return red, nil
end

--- Get an Element form Redis. The key is the element name, the name if the main key if it's a HGET (low level function)
-- @param key The name of the key to retrieve inside the HSET
-- @param name The name if the key for the HSET
-- @return data|nil, errMsg|nil
function _M.getElmFromRedis(key, name)
    --o2debug.debug("getElmFromRedis called for " .. key .. " in " .. name .. " (not cached)")
    local red, err = _M.getRedisInstance()

    if not red then
        return nil, "Failed to connect to redis : " .. tostring(err)
    end

    local data, err = red:hget(name, key)

    -- Make sure we got something from redis
    if not data then
        red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
        o2debug.error("Failed to get redis key for " .. name .. ' : ' ..  tostring(err))
        return nil, "Failed to get redis key for " .. name  ' : ' ..  (err or 'no err msg')
    end

    red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
    -- --o2debug.debug("Data retrieve for " .. name .. ' : ' .. o2debug.varDump(data))
    return data, nil
end

-- Same as getElmFromRedis but will retry multiple times in case of error
function _M.retryableGetElmFromRedis(key, name)
    return retryableRedisGetter(_M.getElmFromRedis, key, name)
end

-- Retrieve the vhost configuration from redis, for both HTTP + HTTPS
-- @param name name of the domain without the 'www.'
-- @return values|nil, errMsg|nil
-- For the return values, the order is important :
-- proxyPassProtocol (1), proxyPassIp (2), proxyPassPort (3), listenToIp (4), proxyPassSslProtocol (5),
-- proxyPassSslIp (6), proxyPassSslPort (7), listenToIp (8), originalBackendIp (9), additionalsParams (10) (json decoded)
function _M.getFromRedis(name)
    --o2debug.debug("getFromRedis called for vhost config of " .. name .. " (not cached)")

    local red, err = _M.getRedisInstance(false)

    if not red or err then
        return nil, "Failed to connect to redis : " .. (err or 'no err msg')
    end

    -- The order of the variables in hmget is important here, be careful about this
    local data, err =  red:hmget(name, 'proxyPassProtocol', 'proxyPassIp', 'proxyPassPort', 'listenToIp',
            'proxyPassSslProtocol', 'proxyPassSslIp', 'proxyPassSslPort', 'listenToIp', 'originalBackendIp',
            'additionalsParams')

    -- Make sure we got something from redis
    if not data then
        red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
        o2debug.error("Failed to get redis key for " .. name .. ' : ' .. tostring(err))
        return nil, "Failed to get redis key for " .. name .. ' : ' .. (err or 'no err msg')
    end

    -- Decode the additionalsParams. Then it will be cached so we avoid having multiple json decode
    if data[10] ~= nil and data[10] ~= "" then
        local ok, waf = pcall(json.decode, data[10])
        if ok and type(waf) == 'table' then
            data[10] = waf
        else
            o2debug.error('Cant json decode we waf config for ' .. name)
        end
    end

    -- Making sure it's something we can handle and not a "userdata"
    if type(data[10]) ~= 'table' then
        data[10] = nil
    end

    red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
    return data, nil
end

-- Set something to the redis HMSET for the domaine
-- @param key The key that will be added or modified in the HSET
-- @param name The key of the HSET (so the domain name)
-- @param value The value...
-- @return bool, err_message
function _M.setElmToRedis(name, key, value)
    -- TODO : MASTER/SLAVE
    local red, err = _M.getRedisInstance(true)
    if not red then
        return false, "Failed to connect to redis : " .. (err or 'no err msg')
    end

    local ok, err = red:hset(name, key, value)
    if not ok then
        red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
        o2debug.error("Fail to set a value for " .. key .. ' for ' .. name .. ' in redis : ' .. tostring(err))
        return false, "Fail to set a value for " .. key .. ' for ' .. name .. ' in redis : ' .. (err or '')
    end

    red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
    return true, nil
end

-- Set something to redis using a HSET
-- @return bool, nil|errorMsg
function _M.hset(key, field, value, ttl)
    if type(key) ~= 'string' then
        o2debug.error("Error, the key is not a string : " .. type(key))
        return false, "Error, the key is not a string"
    end

    if type(field) ~= 'string' then
        o2debug.error("Error, the field is not a string : " .. type(field))
        return false, "Error, the field is not a string"
    end

    if type(value) ~= 'string' and type(value) ~= 'number' then
        o2debug.error( "Error, the value is not a string, neither a number : " .. type(value))
        return false, "Error, the value is not a string, neither a number"
    end

    local red, err = _M.getRedisInstance(true)
    if not red then
        o2debug.debugErr("Failed to connect to master redis")
        return false, "Failed to connect to master redis : " .. tostring(err)
    end

    local ok, err = red:hset(key, field, value)
    if not ok then
        red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
        o2debug.error("Fail to set a value for " .. key .. ':' .. field .. ' in redis : ' .. tostring(err))
        return false, "Fail to set a value for " .. key .. ':' .. field .. ' in redis : ' .. tostring(err)
    end

    if type(ttl) == 'number' then
        red:expire(key, ttl)
    end

    red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
    return true, nil
end

-- Retrieve an element with hget
-- @return string|number|nil, nil|errorMsg
function _M.hget(key, field)
    if type(key) ~= 'string' then
        o2debug.error("Error, the key is not a string : " .. type(field))
        return false, "Error, the key is not a string"
    end

    if type(field) ~= 'string' then
        o2debug.error("Error, the field is not a string : " .. type(field))
        return false, "Error, the field is not a string"
    end

    local red, err = _M.getRedisInstance(false)
    if not red then
        o2debug.debugErr("Failed to connect to master redis")
        return false, "Failed to connect to master redis : " .. tostring(err)
    end

    local res, err = red:hget(key, field)
    if err then
        red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
        o2debug.error("Fail to get a value for " .. key .. ':' .. field .. ' in redis : ' .. tostring(err))
        return false, "Fail to get a value for " .. key .. ':' .. field .. ' in redis : ' .. tostring(err)
    end

    red:set_keepalive(config.redisKeepaliveTimeout, config.redisKeepalivePoolSize)
    return res, nil
end

return _M

Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists