Skip to content

Commit

Permalink
feat: cascade delete of plugins_configurations. fix #107
Browse files Browse the repository at this point in the history
This proposes a way to delete all `plugins_configurations` related to
`Consumers` or `APIs` when deleting one of the two later.

`:delete()` is simply overridden to iterate over all related
`plugins_configuration` and delete them one by one.

Caveats:
- I am very unhappy with how painful it is to use one DAO inside another
DAO. To avoid what would appear to be a cycling reference I rather opted
for instanciating a new DAO inside of the `delete` method.
- It is painful to perform a paginated query through the base_dao.
- It is even more painful to perform a find_by_keys query as a paginated
  query.
  • Loading branch information
thibaultcha committed May 4, 2015
1 parent 617503f commit e3fbadd
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 60 deletions.
33 changes: 33 additions & 0 deletions kong/dao/cassandra/apis.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local BaseDao = require "kong.dao.cassandra.base_dao"
local constants = require "kong.constants"
local PluginsConfigurations = require "kong.dao.cassandra.plugins_configurations"

local SCHEMA = {
id = { type = constants.DATABASE_TYPES.ID },
Expand Down Expand Up @@ -50,4 +51,36 @@ function Apis:new(properties)
Apis.super.new(self, properties)
end

-- @override
function Apis:delete(api_id)
local ok, err = Apis.super.delete(self, api_id)
if not ok then
return err
end

-- delete all related plugins configurations
local plugins_dao = PluginsConfigurations(self._properties)
local query, args_keys, errors = plugins_dao:_build_where_query(plugins_dao._queries.select.query, {
api_id = api_id
})
if errors then
return nil, errors
end

for _, rows, page, err in plugins_dao:_execute_kong_query({query=query, args_keys=args_keys}, {api_id=api_id}, {auto_paging=true}) do
if err then
return nil, err
end

for _, row in ipairs(rows) do
local ok_del_plugin, err = plugins_dao:delete(row.id)
if not ok_del_plugin then
return nil, err
end
end
end

return ok
end

return Apis
62 changes: 39 additions & 23 deletions kong/dao/cassandra/base_dao.lua
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,33 @@ local function encode_cassandra_args(schema, t, args_keys)
return args_to_bind, errors
end

function BaseDao:_build_where_query(query, t)
local args_keys = {}
local where_str = ""
local errors

-- if t is an args_keys, compute a WHERE statement
if t and utils.table_size(t) > 0 then
local where = {}
for k, v in pairs(t) do
if self._schema[k] and self._schema[k].queryable or k == "id" then
table.insert(where, string.format("%s = ?", k))
table.insert(args_keys, k)
else
errors = utils.add_error(errors, k, k.." is not queryable.")
end
end

if errors then
return nil, nil, DaoError(errors, error_types.SCHEMA)
end

where_str = "WHERE "..table.concat(where, " AND ").." ALLOW FILTERING"
end

return string.format(query, where_str), args_keys
end

-- Get a statement from the cache or prepare it (and thus insert it in the cache).
-- The cache key will be the plain string query representation.
-- @param `kong_query` A kong query from the _queries property.
Expand Down Expand Up @@ -267,6 +294,14 @@ function BaseDao:_execute(statement, args, options)
return nil, err
end

if options and options.auto_paging then
local _, rows, page, err = session:execute(statement, args, options)
for i, row in ipairs(rows) do
rows[i] = self:_unmarshall(row)
end
return _, rows, page, err
end

local results, err = session:execute(statement, args, options)
if err then
err = DaoError(err, error_types.DATABASE)
Expand Down Expand Up @@ -521,31 +556,12 @@ end
-- @param `paging_state` Start page from given offset. See lua-resty-cassandra's :execute() option.
-- @return _execute_kong_query()
function BaseDao:find_by_keys(t, page_size, paging_state)
local where, keys = {}, {}
local where_str = ""
local errors

-- if keys are passed, compute a WHERE statement
if t and utils.table_size(t) > 0 then
for k,v in pairs(t) do
if self._schema[k] and self._schema[k].queryable or k == "id" then
table.insert(where, string.format("%s = ?", k))
table.insert(keys, k)
else
errors = utils.add_error(errors, k, k.." is not queryable.")
end
end

if errors then
return nil, DaoError(errors, error_types.SCHEMA)
end

where_str = "WHERE "..table.concat(where, " AND ").." ALLOW FILTERING"
local select_where_query, args_keys, errors = self:_build_where_query(self._queries.select.query, t)
if errors then
return nil, errors
end

local select_query = string.format(self._queries.select.query, where_str)

return self:_execute_kong_query({ query = select_query, args_keys = keys }, t, {
return self:_execute_kong_query({ query = select_where_query, args_keys = args_keys }, t, {
page_size = page_size,
paging_state = paging_state
})
Expand Down
35 changes: 34 additions & 1 deletion kong/dao/cassandra/consumers.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local BaseDao = require "kong.dao.cassandra.base_dao"
local constants = require "kong.constants"
local stringy = require "stringy"
local constants = require "kong.constants"
local PluginsConfigurations = require "kong.dao.cassandra.plugins_configurations"

local function check_custom_id_and_username(value, consumer_t)
if (consumer_t.custom_id == nil or stringy.strip(consumer_t.custom_id) == "")
Expand Down Expand Up @@ -56,4 +57,36 @@ function Consumers:new(properties)
Consumers.super.new(self, properties)
end

-- @override
function Consumers:delete(consumer_id)
local ok, err = Consumers.super.delete(self, consumer_id)
if not ok then
return err
end

-- delete all related plugins configurations
local plugins_dao = PluginsConfigurations(self._properties)
local query, args_keys, errors = plugins_dao:_build_where_query(plugins_dao._queries.select.query, {
consumer_id = consumer_id
})
if errors then
return nil, errors
end

for _, rows, page, err in plugins_dao:_execute_kong_query({query=query, args_keys=args_keys}, {consumer_id=consumer_id}, {auto_paging=true}) do
if err then
return nil, err
end

for _, row in ipairs(rows) do
local ok_del_plugin, err = plugins_dao:delete(row.id)
if not ok_del_plugin then
return nil, err
end
end
end

return ok
end

return Consumers
61 changes: 32 additions & 29 deletions kong/dao/cassandra/factory.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ local LOCALHOST = "localhost"
local LOCALHOST_IP = "127.0.0.1"

-- Converts every occurence of "localhost" to "127.0.0.1"
-- @param host can either be a string or an array of hosts
-- @param `host` A string or an array of hosts
-- @return `host` The normalized host (127.0.0.1)
local function normalize_localhost(host)
if type(host) == "table" then
for i,v in ipairs(host) do
for i, v in ipairs(host) do
if v == LOCALHOST then
host[i] = LOCALHOST_IP
end
Expand All @@ -37,8 +38,8 @@ local function normalize_localhost(host)
return host
end

-- Instanciate a Cassandra DAO.
-- @param properties Cassandra properties
-- Instanciate DAOs for collections.
-- @param `properties` Cassandra properties defined in kong.yml
function CassandraFactory:new(properties)
self.type = "cassandra"
self._properties = properties
Expand All @@ -56,6 +57,7 @@ function CassandraFactory:new(properties)
self.keyauth_credentials = KeyAuthCredentials(properties)
end

-- Drop the database. Erase all data without erasing the schema.
function CassandraFactory:drop()
return self:execute_queries [[
TRUNCATE apis;
Expand All @@ -67,15 +69,13 @@ function CassandraFactory:drop()
]]
end

-- Prepare all statements of collections `._queries` property and put them
-- in a statements cache
--
-- Prepare queries of collections `._queries` property through
-- the base_dao `prepare_kong_statement()` method.
-- Note:
-- Even if the BaseDAO's :_execute_kong_query() method support preparation of statements on-the-go,
-- this method should be called when Kong starts in order to detect any failure in advance
-- as well as test the connection to Cassandra.
--
-- @return error if any
-- Even if the BaseDAO's :_execute_kong_query() method support preparation of statements
-- on-the-go, this method should be called when Kong starts in order to detect any failure
-- in advance and for performance reasons.
-- @return `error` Error if any during execution
function CassandraFactory:prepare()
local function prepare_collection(collection, collection_queries)
if not collection_queries then collection_queries = collection._queries end
Expand Down Expand Up @@ -107,9 +107,10 @@ end

-- Execute a string of queries separated by ;
-- Useful for huge DDL operations such as migrations
-- @param {string} queries Semicolon separated string of queries
-- @param {boolean} no_keyspace Won't set the keyspace if true
-- @return {string} error if any
-- @param `queries` Semicolon separated string of queries
-- @param `no_keyspace` Won't set the keyspace for this query if true.
-- Useful for the first migration query which created the keyspace.
-- @return `error` Error if any
function CassandraFactory:execute_queries(queries, no_keyspace)
local ok, err
local session = cassandra.new()
Expand Down Expand Up @@ -149,11 +150,12 @@ end
local MIGRATION_IDENTIFIER = "migrations"

-- Create a cassandra session and execute a query on given keyspace or default one (from properties).
-- @param query Query or prepared statement given to session:execute
-- @param args List of arguments given to session:execute
-- @param keyspace Optional: overrides properties keyspace if specified
-- @return query result
-- @return error if any
-- Will `:close()` the session instead of putting it back in the socket pool like the base_dao
-- @param `query` Query or prepared statement given to session:execute
-- @param `args` List of arguments given to session:execute
-- @param `keyspace` Optional. overrides properties keyspace if specified
-- @return `results` Results of the query
-- @return `error` Error if any during execution
function CassandraFactory:execute(query, args, keyspace)
local ok, err
local session = cassandra.new()
Expand All @@ -180,18 +182,19 @@ function CassandraFactory:execute(query, args, keyspace)
return ok
end

-- Log (add) given migration to schema_migrations table.
-- @param migration_name Name of the migration to log
-- @return query result
-- @return error if any
-- Log (add) given migration to the schema_migrations table.
-- @param `migration_name` Name of the migration to log
-- @return `results` Results of the query
-- @return `error` Error if any during execution
function CassandraFactory:add_migration(migration_name)
return self:execute("UPDATE schema_migrations SET migrations = migrations + ? WHERE id = ?",
{ cassandra.list({ migration_name }), MIGRATION_IDENTIFIER })
end

-- Return all logged migrations if any. Check if keyspace exists before to avoid error during the first migration.
-- @return A list of previously executed migration (as strings)
-- @return error if any
-- Return all logged migrations if any. Check if keyspace exists before to avoid error
-- if checking before the first migration.
-- @return `migrations` A list of previously executed migration (as strings)
-- @return `error` Error if any during execution
function CassandraFactory:get_migrations()
local rows, err

Expand All @@ -212,8 +215,8 @@ function CassandraFactory:get_migrations()
end

-- Unlog (delete) given migration from the schema_migrations table.
-- @return query result
-- @return error if any
-- @return `results` Results of the query
-- @return `error` Error if any during execution
function CassandraFactory:delete_migration(migration_name)
return self:execute("UPDATE schema_migrations SET migrations = migrations - ? WHERE id = ?",
{ cassandra.list({ migration_name }), MIGRATION_IDENTIFIER })
Expand Down
2 changes: 1 addition & 1 deletion kong/dao/cassandra/plugins_configurations.lua
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ function PluginsConfigurations:find_distinct()
end

local result = {}
for k,_ in pairs(distinct_names) do
for k, _ in pairs(distinct_names) do
table.insert(result, k)
end

Expand Down
8 changes: 4 additions & 4 deletions kong/tools/faker.lua
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Faker.FIXTURES = {
{ name = "keyauth", value = { key_names = { "apikey" }}, __api = 1 },
{ name = "tcplog", value = { host = "127.0.0.1", port = 7777 }, __api = 1 },
{ name = "udplog", value = { host = "127.0.0.1", port = 8888 }, __api = 1 },
{ name = "filelog", value = { }, __api = 1 },
{ name = "filelog", value = {}, __api = 1 },
-- API 2
{ name = "basicauth", value = {}, __api = 2 },
-- API 3
Expand All @@ -73,9 +73,9 @@ Faker.FIXTURES = {
-- API 6
{ name = "cors", value = {}, __api = 6 },
-- API 7
{ name = "cors", value = { origin = "example.com",
methods = "GET",
headers = "origin, type, accepts",
{ name = "cors", value = { origin = "example.com",
methods = "GET",
headers = "origin, type, accepts",
exposed_headers = "x-auth-token",
max_age = 23,
credentials = true }, __api = 7 }
Expand Down
Loading

0 comments on commit e3fbadd

Please sign in to comment.