Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: check database version compatibility #3310

Merged
merged 3 commits into from
Mar 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion kong/cmd/start.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ local function execute(args)
assert(not kill.is_running(conf.nginx_pid),
"Kong is already running in " .. conf.prefix)

local err
local dao = assert(DAOFactory.new(conf))
local ok, err_t = dao:init()
if not ok then
error(tostring(err_t))
end

local err

xpcall(function()
assert(prefix_handler.prepare_prefix(conf, args.nginx_conf))

Expand Down
25 changes: 22 additions & 3 deletions kong/cmd/utils/log.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ local _LEVELS = {
quiet = 6
}

local _NGX_LEVELS = {
[1] = ngx.DEBUG,
-- verbose
[3] = ngx.INFO,
[4] = ngx.WARN,
[5] = ngx.ERR,
-- quiet
}

local r_levels = {}
for k, v in pairs(_LEVELS) do
r_levels[v] = k
Expand Down Expand Up @@ -37,7 +46,7 @@ function _M.enable()
old_lvl = nil
end

local function log(lvl, ...)
function _M.log(lvl, ...)
local format
local args = {...}
if lvl >= log_lvl then
Expand All @@ -47,6 +56,16 @@ local function log(lvl, ...)
end

local msg = string.format(format, unpack(args))

if not ngx.IS_CLI then
local ngx_lvl = _NGX_LEVELS[lvl]
if ngx_lvl then
ngx.log(ngx_lvl, msg)
end

return
end

if log_lvl < _LEVELS.info or lvl >= _LEVELS.warn then
msg = string.format("%s [%s] %s", os.date("%Y/%m/%d %H:%M:%S"), r_levels[lvl], msg)
end
Expand All @@ -61,12 +80,12 @@ end

return setmetatable(_M, {
__call = function(_, ...)
return log(_LEVELS.info, ...)
return _M.log(_LEVELS.info, ...)
end,
__index = function(t, key)
if _LEVELS[key] then
return function(...)
log(_LEVELS[key], ...)
_M.log(_LEVELS[key], ...)
end
end
return rawget(t, key)
Expand Down
10 changes: 10 additions & 0 deletions kong/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,14 @@ return {
"kong_cluster_events",
"kong_healthchecks",
},
DATABASE = {
POSTGRES = {
MIN = "9.5",
DEPRECATED = "9.4",
},
CASSANDRA = {
MIN = "2.2",
DEPRECATED = "2.1",
}
}
}
1 change: 1 addition & 0 deletions kong/dao/db/cassandra.lua
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ end

function _M:infos()
return {
db_name = "Cassandra",
desc = "keyspace",
name = self.cluster_options.keyspace,
version = self.major_minor_version or "unknown",
Expand Down
1 change: 1 addition & 0 deletions kong/dao/db/postgres.lua
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ end

function _M:infos()
return {
db_name = "PostgreSQL",
desc = "database",
name = self:clone_query_options().database,
version = self.major_minor_version or "unknown",
Expand Down
57 changes: 48 additions & 9 deletions kong/dao/factory.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
local DAO = require "kong.dao.dao"
local log = require "kong.cmd.utils.log"
local utils = require "kong.tools.utils"
local version = require "version"
local constants = require "kong.constants"
local ModelFactory = require "kong.dao.model_factory"

local fmt = string.format

local CORE_MODELS = {
"apis",
"consumers",
Expand Down Expand Up @@ -128,8 +133,50 @@ function _M.new(kong_config)
return setmetatable(self, _M)
end

function _M:check_version_compat(min, deprecated)
local db_infos = self:infos()
if db_infos.version == "unknown" then
return nil, "could not check database compatibility: version " ..
"is unknown (did you call ':init'?)"
end

local db_v = version.version(db_infos.version)
local min_v = version.version(min)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can update the 'version.lua' library to 1.0 and change those into version(min) without the double 'version' name

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I saw; but as usual when possible, we'll make that a separate contribution


if db_v < min_v then
if deprecated then
local depr_v = version.version(deprecated)

if db_v >= depr_v then
log.warn("Currently using %s %s which is considered deprecated, " ..
"please use %s or greater", db_infos.db_name,
db_infos.version, min)

return true
end
end

return nil, fmt("Kong requires %s %s or greater (currently using %s)",
db_infos.db_name, min, db_infos.version)
end

return true
end

function _M:init()
return self.db:init()
local ok, err = self.db:init()
if not ok then
return nil, err
end

local db_constants = constants.DATABASE[self.db_type:upper()]

ok, err = self:check_version_compat(db_constants.MIN, db_constants.DEPRECATED)
if not ok then
return nil, err
end

return true
end

function _M:init_worker()
Expand Down Expand Up @@ -296,13 +343,11 @@ local function migrate(self, identifier, migrations_modules, cur_migrations, on_
end

local function default_on_migrate(identifier, db_infos)
local log = require "kong.cmd.utils.log"
log("migrating %s for %s %s",
identifier, db_infos.desc, db_infos.name)
end

local function default_on_success(identifier, migration_name, db_infos)
local log = require "kong.cmd.utils.log"
log("%s migrated up to: %s",
identifier, migration_name)
end
Expand All @@ -319,8 +364,6 @@ function _M:are_migrations_uptodate()
"could not retrieve current migrations: " .. err)
end

local log = require "kong.cmd.utils.log"

for module, migrations in pairs(migrations_modules) do
for _, migration in ipairs(migrations) do
if not (cur_migrations[module] and
Expand Down Expand Up @@ -350,8 +393,6 @@ function _M:check_schema_consensus()
return true -- only applicable for cassandra
end

local log = require "kong.cmd.utils.log"

log.verbose("checking Cassandra schema consensus...")

local ok, err = self.db:check_schema_consensus()
Expand All @@ -370,8 +411,6 @@ function _M:run_migrations(on_migrate, on_success)
on_migrate = on_migrate or default_on_migrate
on_success = on_success or default_on_success

local log = require "kong.cmd.utils.log"

log.verbose("running datastore migrations")

if self.db.name == "cassandra" then
Expand Down
146 changes: 145 additions & 1 deletion spec/02-integration/03-dao/01-factory_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ helpers.for_each_dao(function(kong_conf)

if kong_conf.database == "postgres" then
assert.same({
db_name = "PostgreSQL",
desc = "database",
name = kong_conf.pg_database,
version = "unknown",
}, info)

elseif kong_conf.database == "cassandra" then
assert.same({
db_name = "Cassandra",
desc = "keyspace",
name = kong_conf.cassandra_keyspace,
version = "unknown",
Expand All @@ -50,7 +52,17 @@ helpers.for_each_dao(function(kong_conf)

local info = factory:infos()
assert.is_string(info.version)
assert.not_equal("unknown", info.version)
-- should be <major>.<minor>
assert.matches("%d+%.%d+", info.version)
end)

it("calls :check_version_compat()", function()
local factory = assert(Factory.new(kong_conf))
local s = spy.on(factory, "check_version_compat")

factory:init()

assert.spy(s).was_called()
end)

if kong_conf.database == "cassandra" then
Expand All @@ -62,4 +74,136 @@ helpers.for_each_dao(function(kong_conf)
end)
end
end)

describe(":check_version_compat()", function()
local factory
local db_name

before_each(function()
factory = assert(Factory.new(kong_conf))

local db_infos = factory:infos()
db_name = db_infos.db_name
end)

it("errors if init() was not called", function()
local ok, err = factory:check_version_compat()
assert.is_nil(ok)
assert.equal("could not check database compatibility: version " ..
"is unknown (did you call ':init'?)", err)
end)

describe("db_ver < min", function()
it("errors", function()
local versions_to_test = {
"1.0",
"9.0",
"9.3",
}

for _, v in ipairs(versions_to_test) do
factory.db.major_minor_version = v

local ok, err = factory:check_version_compat("10.0")
assert.is_nil(ok)
assert.equal("Kong requires " .. db_name .. " 10.0 or greater " ..
"(currently using " .. v .. ")", err)
end
end)
end)

describe("db_ver < deprecated < min", function()
it("errors", function()
local versions_to_test = {
"1.0",
"9.0",
"9.3",
}

for _, v in ipairs(versions_to_test) do
factory.db.major_minor_version = v

local ok, err = factory:check_version_compat("10.0", "9.4")
assert.is_nil(ok)
assert.equal("Kong requires " .. db_name .. " 10.0 or greater " ..
"(currently using " .. v .. ")", err)
end
end)
end)

describe("deprecated <= db_ver < min", function()
it("logs deprecation warning", function()
local log = require "kong.cmd.utils.log"
local s = spy.on(log, "log")

local versions_to_test = {
"9.3",
"9.4",
}

for _, v in ipairs(versions_to_test) do
factory.db.major_minor_version = v

local ok, err = factory:check_version_compat("9.5", "9.3")
assert.is_nil(err)
assert.is_true(ok) -- no error on deprecation notices
assert.spy(s).was_called_with(log.levels.warn,
"Currently using %s %s which is considered deprecated, " ..
"please use %s or greater", db_name, v, "9.5")
end
end)
end)

describe("min < deprecated <= db_ver", function()
-- Note: constants should not be configured in this fashion, but this
-- test is for robustness's sake
it("fine", function()
local versions_to_test = {
"10.0",
"11.1",
}

for _, v in ipairs(versions_to_test) do
factory.db.major_minor_version = v

local ok, err = factory:check_version_compat("9.4", "10.0")
assert.is_nil(err)
assert.is_true(ok)
end
end)
end)

describe("deprecated < min <= db_ver", function()
it("fine", function()
local versions_to_test = {
"9.5",
"10.0",
"11.1",
}

for _, v in ipairs(versions_to_test) do
factory.db.major_minor_version = v

local ok, err = factory:check_version_compat("9.5", "9.4")
assert.is_nil(err)
assert.is_true(ok)
end
end)
end)

it("asserts current constants", function()
-- current min versions hard-coded in the tests to see them fail when we
-- update the constants
factory.db.major_minor_version = kong_conf.database == "postgres" and
"9.5" or "2.2"

local constants = require "kong.constants"
local db_constants = constants.DATABASE[kong_conf.database:upper()]

local ok, err = factory:check_version_compat(db_constants.MIN,
db_constants.DEPRECATED)
assert.is_nil(err)
assert.is_true(ok)
end)
end)
end)