From 067aad4aa80641b756e3a95b2ab96da99b735248 Mon Sep 17 00:00:00 2001 From: thefosk Date: Sat, 17 Oct 2015 03:03:38 -0700 Subject: [PATCH] Adding cache API - CLI Rewrite - Serf integration - Hooks/Events - Invalidations --- .ci/setup_kong.sh | 2 +- .luacheckrc | 2 +- bin/kong | 25 +- kong-0.5.2-1.rockspec | 52 ++- kong.yml | 32 +- kong/api/app.lua | 2 +- kong/api/crud_helpers.lua | 2 +- kong/api/route_helpers.lua | 8 - kong/api/routes/cache.lua | 33 ++ kong/api/routes/cluster.lua | 58 +++ kong/api/routes/kong.lua | 3 +- kong/cli/cmds/cluster.lua | 52 +++ kong/cli/{ => cmds}/config.lua | 21 +- kong/cli/{ => cmds}/db.lua | 18 +- kong/cli/{ => cmds}/migrations.lua | 73 ++-- kong/cli/cmds/quit.lua | 35 ++ kong/cli/cmds/reload.lua | 36 ++ kong/cli/cmds/restart.lua | 17 + kong/cli/cmds/start.lua | 32 ++ kong/cli/cmds/stop.lua | 24 ++ kong/cli/cmds/version.lua | 6 + kong/cli/quit.lua | 26 -- kong/cli/reload.lua | 27 -- kong/cli/restart.lua | 30 -- kong/cli/services/base_service.lua | 94 +++++ kong/cli/services/dnsmasq.lua | 42 ++ kong/cli/services/nginx.lua | 263 ++++++++++++ kong/cli/services/serf.lua | 155 +++++++ kong/cli/start.lua | 27 -- kong/cli/stop.lua | 26 -- kong/cli/utils/configuration.lua | 26 ++ kong/cli/utils/dnsmasq.lua | 44 -- kong/cli/utils/logger.lua | 50 +++ kong/cli/utils/luarocks.lua | 47 +++ kong/cli/utils/services.lua | 30 ++ kong/cli/utils/signal.lua | 328 --------------- kong/cli/utils/ssl.lua | 16 +- kong/cli/utils/utils.lua | 137 ------ kong/cli/version.lua | 6 - kong/constants.lua | 4 +- kong/core/events.lua | 30 ++ kong/core/hooks.lua | 39 ++ kong/core/resolver.lua | 2 +- kong/dao/cassandra/apis.lua | 4 +- kong/dao/cassandra/base_dao.lua | 46 +- kong/dao/cassandra/consumers.lua | 4 +- kong/dao/cassandra/factory.lua | 5 +- kong/dao/cassandra/migrations.lua | 4 +- kong/dao/cassandra/plugins.lua | 4 +- kong/dao/schemas/plugins.lua | 14 + kong/kong.lua | 29 +- kong/plugins/acl/api.lua | 2 +- kong/plugins/acl/daos.lua | 23 +- kong/plugins/acl/hooks.lua | 23 + kong/plugins/basic-auth/daos.lua | 21 +- kong/plugins/basic-auth/hooks.lua | 17 + kong/plugins/hmac-auth/access.lua | 6 +- kong/plugins/hmac-auth/daos.lua | 9 +- kong/plugins/hmac-auth/hooks.lua | 17 + kong/plugins/jwt/access.lua | 6 +- kong/plugins/jwt/daos.lua | 9 +- kong/plugins/jwt/hooks.lua | 17 + kong/plugins/key-auth/daos.lua | 9 +- kong/plugins/key-auth/hooks.lua | 17 + kong/plugins/oauth2/api.lua | 30 ++ kong/plugins/oauth2/daos.lua | 26 +- kong/plugins/oauth2/hooks.lua | 20 + kong/plugins/rate-limiting/daos.lua | 4 +- kong/plugins/response-ratelimiting/daos.lua | 4 +- kong/plugins/ssl/hooks.lua | 19 + kong/plugins/ssl/schema.lua | 17 +- kong/plugins/ssl/ssl_util.lua | 14 +- kong/tools/config_defaults.lua | 12 +- kong/tools/config_loader.lua | 10 +- kong/tools/dao_loader.lua | 6 +- kong/tools/database_cache.lua | 56 ++- kong/tools/io.lua | 5 +- kong/tools/printable.lua | 2 +- kong/tools/utils.lua | 12 + .../admin_api/cache_routes_spec.lua | 90 ++++ .../admin_api/cluster_routes_spec.lua | 55 +++ .../admin_api/kong_routes_spec.lua | 1 - .../admin_api/route_helpers_spec.lua | 3 +- spec/integration/cli/{ => cmds}/quit_spec.lua | 0 .../cli/{ => cmds}/reload_spec.lua | 0 .../cli/{ => cmds}/restart_spec.lua | 11 +- .../integration/cli/{ => cmds}/start_spec.lua | 0 .../cli/{ => cmds}/version_spec.lua | 0 .../integration/cli/services/dnsmasq_spec.lua | 31 ++ spec/integration/cli/services/nginx_spec.lua | 131 ++++++ spec/integration/cli/services/serf_spec.lua | 31 ++ spec/integration/cli/utils/luarocks_spec.lua | 21 + spec/integration/core/hooks_spec.lua | 395 ++++++++++++++++++ .../dao/cassandra/cascade_spec.lua | 4 +- .../integration/dao/cassandra/events_spec.lua | 123 ++++++ .../integration/proxy/database_cache_spec.lua | 67 --- spec/plugins/acl/api_spec.lua | 2 +- spec/plugins/acl/hooks_spec.lua | 163 ++++++++ spec/plugins/basic-auth/crypto_spec.lua | 15 + spec/plugins/basic-auth/hooks_spec.lua | 144 +++++++ spec/plugins/hmac-auth/hooks_spec.lua | 164 ++++++++ spec/plugins/jwt/hooks_spec.lua | 158 +++++++ spec/plugins/key-auth/hooks_spec.lua | 145 +++++++ spec/plugins/oauth2/hooks_spec.lua | 302 +++++++++++++ spec/plugins/ssl/hooks_spec.lua | 166 ++++++++ spec/spec_helpers.lua | 16 +- spec/unit/cli/utils_spec.lua | 12 - spec/unit/rockspec_spec.lua | 54 +++ spec/unit/tools/database_cache_spec.lua | 19 +- 109 files changed, 3849 insertions(+), 979 deletions(-) create mode 100644 kong/api/routes/cache.lua create mode 100644 kong/api/routes/cluster.lua create mode 100644 kong/cli/cmds/cluster.lua rename kong/cli/{ => cmds}/config.lua (82%) rename kong/cli/{ => cmds}/db.lua (75%) rename kong/cli/{ => cmds}/migrations.lua (61%) create mode 100644 kong/cli/cmds/quit.lua create mode 100644 kong/cli/cmds/reload.lua create mode 100644 kong/cli/cmds/restart.lua create mode 100755 kong/cli/cmds/start.lua create mode 100755 kong/cli/cmds/stop.lua create mode 100644 kong/cli/cmds/version.lua delete mode 100644 kong/cli/quit.lua delete mode 100644 kong/cli/reload.lua delete mode 100644 kong/cli/restart.lua create mode 100644 kong/cli/services/base_service.lua create mode 100644 kong/cli/services/dnsmasq.lua create mode 100644 kong/cli/services/nginx.lua create mode 100644 kong/cli/services/serf.lua delete mode 100755 kong/cli/start.lua delete mode 100755 kong/cli/stop.lua create mode 100644 kong/cli/utils/configuration.lua delete mode 100644 kong/cli/utils/dnsmasq.lua create mode 100644 kong/cli/utils/logger.lua create mode 100644 kong/cli/utils/luarocks.lua create mode 100644 kong/cli/utils/services.lua delete mode 100644 kong/cli/utils/signal.lua delete mode 100644 kong/cli/utils/utils.lua delete mode 100644 kong/cli/version.lua create mode 100644 kong/core/events.lua create mode 100644 kong/core/hooks.lua create mode 100644 kong/plugins/acl/hooks.lua create mode 100644 kong/plugins/basic-auth/hooks.lua create mode 100644 kong/plugins/hmac-auth/hooks.lua create mode 100644 kong/plugins/jwt/hooks.lua create mode 100644 kong/plugins/key-auth/hooks.lua create mode 100644 kong/plugins/oauth2/hooks.lua create mode 100644 kong/plugins/ssl/hooks.lua create mode 100644 spec/integration/admin_api/cache_routes_spec.lua create mode 100644 spec/integration/admin_api/cluster_routes_spec.lua rename spec/integration/cli/{ => cmds}/quit_spec.lua (100%) rename spec/integration/cli/{ => cmds}/reload_spec.lua (100%) rename spec/integration/cli/{ => cmds}/restart_spec.lua (66%) rename spec/integration/cli/{ => cmds}/start_spec.lua (100%) rename spec/integration/cli/{ => cmds}/version_spec.lua (100%) create mode 100644 spec/integration/cli/services/dnsmasq_spec.lua create mode 100644 spec/integration/cli/services/nginx_spec.lua create mode 100644 spec/integration/cli/services/serf_spec.lua create mode 100644 spec/integration/cli/utils/luarocks_spec.lua create mode 100644 spec/integration/core/hooks_spec.lua create mode 100644 spec/integration/dao/cassandra/events_spec.lua delete mode 100644 spec/integration/proxy/database_cache_spec.lua create mode 100644 spec/plugins/acl/hooks_spec.lua create mode 100644 spec/plugins/basic-auth/crypto_spec.lua create mode 100644 spec/plugins/basic-auth/hooks_spec.lua create mode 100644 spec/plugins/hmac-auth/hooks_spec.lua create mode 100644 spec/plugins/jwt/hooks_spec.lua create mode 100644 spec/plugins/key-auth/hooks_spec.lua create mode 100644 spec/plugins/oauth2/hooks_spec.lua create mode 100644 spec/plugins/ssl/hooks_spec.lua delete mode 100644 spec/unit/cli/utils_spec.lua create mode 100644 spec/unit/rockspec_spec.lua diff --git a/.ci/setup_kong.sh b/.ci/setup_kong.sh index 06b07414487..b1bfb98596f 100644 --- a/.ci/setup_kong.sh +++ b/.ci/setup_kong.sh @@ -1,6 +1,6 @@ #!/bin/bash -KONG_VERSION=0.5.0 +KONG_VERSION=0.5.2 sudo apt-get update diff --git a/.luacheckrc b/.luacheckrc index b5652c1fca3..7bb2fdc36bd 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,6 +1,6 @@ redefined = false unused_args = false -globals = {"ngx", "dao", "app", "configuration"} +globals = {"ngx", "dao", "app", "configuration", "events"} files["kong/"] = { std = "luajit" diff --git a/bin/kong b/bin/kong index d34526c1fba..4fbf1524c81 100755 --- a/bin/kong +++ b/bin/kong @@ -10,19 +10,20 @@ -- This script is not parsed by lapp due to limitations of the said framework as it -- is currently implemented. -local cutils = require "kong.cli.utils" -local infos = cutils.get_kong_infos() +local luarocks = require "kong.cli.utils.luarocks" +local infos = luarocks.get_kong_infos() local commands = { - db = "kong.cli.db", - stop = "kong.cli.stop", - quit = "kong.cli.quit", - start = "kong.cli.start", - reload = "kong.cli.reload", - config = "kong.cli.config", - restart = "kong.cli.restart", - version = "kong.cli.version", - ["--version"] = "kong.cli.version", - migrations = "kong.cli.migrations" + db = "kong.cli.cmds.db", + stop = "kong.cli.cmds.stop", + quit = "kong.cli.cmds.quit", + start = "kong.cli.cmds.start", + reload = "kong.cli.cmds.reload", + config = "kong.cli.cmds.config", + restart = "kong.cli.cmds.restart", + version = "kong.cli.cmds.version", + ["--version"] = "kong.cli.cmds.version", + migrations = "kong.cli.cmds.migrations", + cluster = "kong.cli.cmds.cluster" } local help_message = string.format([[ diff --git a/kong-0.5.2-1.rockspec b/kong-0.5.2-1.rockspec index 5386e622f95..ff7ffee30bf 100644 --- a/kong-0.5.2-1.rockspec +++ b/kong-0.5.2-1.rockspec @@ -14,7 +14,7 @@ dependencies = { "lua ~> 5.1", "luasec ~> 0.5-2", - "lua_uuid ~> 0.1-4", + "lua_uuid ~> 0.1-7", "luatz ~> 0.3-1", "yaml ~> 1.1.2-1", "lapis ~> 1.3.0-1", @@ -26,6 +26,7 @@ dependencies = { "ansicolors ~> 1.0.2-3", "lbase64 ~> 20120820-1", "lua-resty-iputils ~> 0.2.0-1", + "mediator_lua ~> 1.1.2-0", "luasocket ~> 2.0.2-6", "lrexlib-pcre ~> 2.7.2-1", @@ -45,20 +46,26 @@ build = { ["kong.constants"] = "kong/constants.lua", - ["kong.cli.utils"] = "kong/cli/utils/utils.lua", - ["kong.cli.utils.dnsmasq"] = "kong/cli/utils/dnsmasq.lua", + ["kong.cli.utils.logger"] = "kong/cli/utils/logger.lua", + ["kong.cli.utils.luarocks"] = "kong/cli/utils/luarocks.lua", ["kong.cli.utils.ssl"] = "kong/cli/utils/ssl.lua", - ["kong.cli.utils.signal"] = "kong/cli/utils/signal.lua", ["kong.cli.utils.input"] = "kong/cli/utils/input.lua", - ["kong.cli.db"] = "kong/cli/db.lua", - ["kong.cli.config"] = "kong/cli/config.lua", - ["kong.cli.quit"] = "kong/cli/quit.lua", - ["kong.cli.stop"] = "kong/cli/stop.lua", - ["kong.cli.start"] = "kong/cli/start.lua", - ["kong.cli.reload"] = "kong/cli/reload.lua", - ["kong.cli.restart"] = "kong/cli/restart.lua", - ["kong.cli.version"] = "kong/cli/version.lua", - ["kong.cli.migrations"] = "kong/cli/migrations.lua", + ["kong.cli.utils.services"] = "kong/cli/utils/services.lua", + ["kong.cli.utils.configuration"] = "kong/cli/utils/configuration.lua", + ["kong.cli.cmds.db"] = "kong/cli/cmds/db.lua", + ["kong.cli.cmds.config"] = "kong/cli/cmds/config.lua", + ["kong.cli.cmds.quit"] = "kong/cli/cmds/quit.lua", + ["kong.cli.cmds.stop"] = "kong/cli/cmds/stop.lua", + ["kong.cli.cmds.start"] = "kong/cli/cmds/start.lua", + ["kong.cli.cmds.reload"] = "kong/cli/cmds/reload.lua", + ["kong.cli.cmds.restart"] = "kong/cli/cmds/restart.lua", + ["kong.cli.cmds.version"] = "kong/cli/cmds/version.lua", + ["kong.cli.cmds.migrations"] = "kong/cli/cmds/migrations.lua", + ["kong.cli.cmds.cluster"] = "kong/cli/cmds/cluster.lua", + ["kong.cli.services.base_service"] = "kong/cli/services/base_service.lua", + ["kong.cli.services.dnsmasq"] = "kong/cli/services/dnsmasq.lua", + ["kong.cli.services.serf"] = "kong/cli/services/serf.lua", + ["kong.cli.services.nginx"] = "kong/cli/services/nginx.lua", ["kong.tools.io"] = "kong/tools/io.lua", ["kong.tools.utils"] = "kong/tools/utils.lua", @@ -79,7 +86,9 @@ build = { ["kong.core.certificate"] = "kong/core/certificate.lua", ["kong.core.resolver"] = "kong/core/resolver.lua", ["kong.core.plugins_iterator"] = "kong/core/plugins_iterator.lua", + ["kong.core.hooks"] = "kong/core/hooks.lua", ["kong.core.reports"] = "kong/core/reports.lua", + ["kong.core.events"] = "kong/core/events.lua", ["kong.dao.cassandra.schema.migrations"] = "kong/dao/cassandra/schema/migrations.lua", ["kong.dao.error"] = "kong/dao/error.lua", @@ -102,12 +111,14 @@ build = { ["kong.plugins.basic-auth.handler"] = "kong/plugins/basic-auth/handler.lua", ["kong.plugins.basic-auth.access"] = "kong/plugins/basic-auth/access.lua", ["kong.plugins.basic-auth.schema"] = "kong/plugins/basic-auth/schema.lua", + ["kong.plugins.basic-auth.hooks"] = "kong/plugins/basic-auth/hooks.lua", ["kong.plugins.basic-auth.api"] = "kong/plugins/basic-auth/api.lua", ["kong.plugins.basic-auth.daos"] = "kong/plugins/basic-auth/daos.lua", ["kong.plugins.key-auth.migrations.cassandra"] = "kong/plugins/key-auth/migrations/cassandra.lua", ["kong.plugins.key-auth.handler"] = "kong/plugins/key-auth/handler.lua", ["kong.plugins.key-auth.access"] = "kong/plugins/key-auth/access.lua", + ["kong.plugins.key-auth.hooks"] = "kong/plugins/key-auth/hooks.lua", ["kong.plugins.key-auth.schema"] = "kong/plugins/key-auth/schema.lua", ["kong.plugins.key-auth.api"] = "kong/plugins/key-auth/api.lua", ["kong.plugins.key-auth.daos"] = "kong/plugins/key-auth/daos.lua", @@ -115,6 +126,7 @@ build = { ["kong.plugins.oauth2.migrations.cassandra"] = "kong/plugins/oauth2/migrations/cassandra.lua", ["kong.plugins.oauth2.handler"] = "kong/plugins/oauth2/handler.lua", ["kong.plugins.oauth2.access"] = "kong/plugins/oauth2/access.lua", + ["kong.plugins.oauth2.hooks"] = "kong/plugins/oauth2/hooks.lua", ["kong.plugins.oauth2.schema"] = "kong/plugins/oauth2/schema.lua", ["kong.plugins.oauth2.daos"] = "kong/plugins/oauth2/daos.lua", ["kong.plugins.oauth2.api"] = "kong/plugins/oauth2/api.lua", @@ -177,6 +189,7 @@ build = { ["kong.plugins.ssl.handler"] = "kong/plugins/ssl/handler.lua", ["kong.plugins.ssl.certificate"] = "kong/plugins/ssl/certificate.lua", ["kong.plugins.ssl.access"] = "kong/plugins/ssl/access.lua", + ["kong.plugins.ssl.hooks"] = "kong/plugins/ssl/hooks.lua", ["kong.plugins.ssl.ssl_util"] = "kong/plugins/ssl/ssl_util.lua", ["kong.plugins.ssl.schema"] = "kong/plugins/ssl/schema.lua", @@ -189,13 +202,7 @@ build = { ["kong.plugins.acl.handler"] = "kong/plugins/acl/handler.lua", ["kong.plugins.acl.access"] = "kong/plugins/acl/access.lua", ["kong.plugins.acl.schema"] = "kong/plugins/acl/schema.lua", - ["kong.plugins.acl.api"] = "kong/plugins/acl/api.lua", - ["kong.plugins.acl.daos"] = "kong/plugins/acl/daos.lua", - - ["kong.plugins.acl.migrations.cassandra"] = "kong/plugins/acl/migrations/cassandra.lua", - ["kong.plugins.acl.handler"] = "kong/plugins/acl/handler.lua", - ["kong.plugins.acl.access"] = "kong/plugins/acl/access.lua", - ["kong.plugins.acl.schema"] = "kong/plugins/acl/schema.lua", + ["kong.plugins.acl.hooks"] = "kong/plugins/acl/hooks.lua", ["kong.plugins.acl.api"] = "kong/plugins/acl/api.lua", ["kong.plugins.acl.daos"] = "kong/plugins/acl/daos.lua", @@ -206,12 +213,14 @@ build = { ["kong.api.routes.apis"] = "kong/api/routes/apis.lua", ["kong.api.routes.consumers"] = "kong/api/routes/consumers.lua", ["kong.api.routes.plugins"] = "kong/api/routes/plugins.lua", - ["kong.api.routes.plugins"] = "kong/api/routes/plugins.lua", + ["kong.api.routes.cache"] = "kong/api/routes/cache.lua", + ["kong.api.routes.cluster"] = "kong/api/routes/cluster.lua", ["kong.plugins.jwt.migrations.cassandra"] = "kong/plugins/jwt/migrations/cassandra.lua", ["kong.plugins.jwt.handler"] = "kong/plugins/jwt/handler.lua", ["kong.plugins.jwt.access"] = "kong/plugins/jwt/access.lua", ["kong.plugins.jwt.schema"] = "kong/plugins/jwt/schema.lua", + ["kong.plugins.jwt.hooks"] = "kong/plugins/jwt/hooks.lua", ["kong.plugins.jwt.api"] = "kong/plugins/jwt/api.lua", ["kong.plugins.jwt.daos"] = "kong/plugins/jwt/daos.lua", ["kong.plugins.jwt.jwt_parser"] = "kong/plugins/jwt/jwt_parser.lua", @@ -220,6 +229,7 @@ build = { ["kong.plugins.hmac-auth.handler"] = "kong/plugins/hmac-auth/handler.lua", ["kong.plugins.hmac-auth.access"] = "kong/plugins/hmac-auth/access.lua", ["kong.plugins.hmac-auth.schema"] = "kong/plugins/hmac-auth/schema.lua", + ["kong.plugins.hmac-auth.hooks"] = "kong/plugins/hmac-auth/hooks.lua", ["kong.plugins.hmac-auth.api"] = "kong/plugins/hmac-auth/api.lua", ["kong.plugins.hmac-auth.daos"] = "kong/plugins/hmac-auth/daos.lua", diff --git a/kong.yml b/kong.yml index b6e8f779930..cad41363b1c 100644 --- a/kong.yml +++ b/kong.yml @@ -51,6 +51,32 @@ ## manage your Kong infrastructure. It needs to be secured appropriatly. # admin_api_port: 8001 +###### +## Port on which Kong will start dnsmasq. +# dnsmasq_port: 8053 + +###### +## Cluster settings +# cluster: + + ###### + ## Address to bind network listeners to. + # bind: "0.0.0.0:7946" + + ###### + ## Network interface to bind to. Can be used instead of bind if the interface is known but + ## not the address. If both are provided, then Kong verifies that the interface has the bind + ## address that is provided. + # iface: "" + + ###### + ## Address to bind the RPC listener. + # rpc-addr: "127.0.0.1:7373" + + ###### + ## Key for encrypting network traffic within Kong. Must be a base64-encoded 16-byte key. + # encrypt: "foo" + ###### ## Specify which database to use from the databases_available property. # database: cassandra @@ -114,12 +140,6 @@ # user: cassandra # password: cassandra -###### -## Time (in seconds) for which entities from the database (APIs, plugins configurations...) -## are cached by Kong. Increase this value if you want to lower the number of requests made -## to your database. -# database_cache_expiration: 5 - ###### ## SSL certificates to use. # ssl_cert_path: /path/to/certificate.pem diff --git a/kong/api/app.lua b/kong/api/app.lua index 8e80c303d81..f59e0f1f2ed 100644 --- a/kong/api/app.lua +++ b/kong/api/app.lua @@ -140,7 +140,7 @@ local function attach_routes(routes) end -- Load core routes -for _, v in ipairs({"kong", "apis", "consumers", "plugins"}) do +for _, v in ipairs({"kong", "apis", "consumers", "plugins", "cache", "cluster" }) do local routes = require("kong.api.routes."..v) attach_routes(routes) end diff --git a/kong/api/crud_helpers.lua b/kong/api/crud_helpers.lua index 1ac78665832..e0cb0084c1e 100644 --- a/kong/api/crud_helpers.lua +++ b/kong/api/crud_helpers.lua @@ -146,4 +146,4 @@ function _M.delete(where_t, dao_collection) end end -return _M +return _M \ No newline at end of file diff --git a/kong/api/route_helpers.lua b/kong/api/route_helpers.lua index 0971b5a68e3..420e17651bd 100644 --- a/kong/api/route_helpers.lua +++ b/kong/api/route_helpers.lua @@ -2,14 +2,6 @@ local stringy = require "stringy" local _M = {} -function _M.get_hostname() - local f = io.popen ("/bin/hostname") - local hostname = f:read("*a") or "" - f:close() - hostname = string.gsub(hostname, "\n$", "") - return hostname -end - function _M.parse_status(value) local result = {} local parts = stringy.split(value, "\n") diff --git a/kong/api/routes/cache.lua b/kong/api/routes/cache.lua new file mode 100644 index 00000000000..b58d0fed904 --- /dev/null +++ b/kong/api/routes/cache.lua @@ -0,0 +1,33 @@ +local responses = require "kong.tools.responses" +local cache = require "kong.tools.database_cache" + +return { + ["/cache/"] = { + DELETE = function(self, dao_factory) + cache.delete_all() + return responses.send_HTTP_OK() + end + }, + + ["/cache/:key"] = { + GET = function(self, dao_factory) + if self.params.key then + local cached_item = cache.get(self.params.key) + if cached_item then + return responses.send_HTTP_OK(cached_item) + end + end + + return responses.send_HTTP_NOT_FOUND() + end, + + DELETE = function(self, dao_factory) + if self.params.key then + cache.delete(self.params.key) + return responses.send_HTTP_OK() + else + return responses.send_HTTP_NOT_FOUND() + end + end + } +} diff --git a/kong/api/routes/cluster.lua b/kong/api/routes/cluster.lua new file mode 100644 index 00000000000..63cad0ec063 --- /dev/null +++ b/kong/api/routes/cluster.lua @@ -0,0 +1,58 @@ +local responses = require "kong.tools.responses" +local cjson = require "cjson" + +return { + ["/cluster/"] = { + GET = function(self, dao_factory) + local serf = require("kong.cli.services.serf")(configuration) + local res, err = serf:invoke_signal("members", { ["-format"] = "json" }) + if err then + return responses.send_HTTP_INTERNAL_SERVER_ERROR(err) + else + local members = cjson.decode(res).members + for _, v in ipairs(members) do + v.tags = nil + v.protocol = nil + end + return responses.send_HTTP_OK(members) + end + end, + + POST = function(self, dao_factory) + local serf = require("kong.cli.services.serf")(configuration) + if self.params.address then + local _, err = serf:invoke_signal("join", { self.params.address }) + if err then + return responses.send_HTTP_BAD_REQUEST(err) + else + return responses.send_HTTP_OK() + end + else + return responses.send_HTTP_BAD_REQUEST("Missing \"address\"") + end + end + }, + ["/cluster/events/"] = { + POST = function(self, dao_factory) + ngx.log(ngx.DEBUG, " received cluster event "..(self.params.type and self.params.type or "UNKNOWN")) + + local message_t = self.params + + -- If it's an update, load the new entity too so it's available in the hooks + if message_t.type == events.TYPES.ENTITY_UPDATED then + message_t.old_entity = message_t.entity + message_t.entity = dao[message_t.collection]:find_by_primary_key({id=message_t.old_entity.id}) + if not message_t.entity then + -- This means that the entity has been deleted immediately after an update in the meanwhile that + -- the system was still processing the update. A delete invalidation will come immediately after + -- so we can ignore this event + return responses.send_HTTP_OK() + end + end + + -- Trigger event in the node + events:publish(message_t.type, message_t) + return responses.send_HTTP_OK() + end + } +} diff --git a/kong/api/routes/kong.lua b/kong/api/routes/kong.lua index fbd5e65557f..9658e16f312 100644 --- a/kong/api/routes/kong.lua +++ b/kong/api/routes/kong.lua @@ -1,5 +1,6 @@ local constants = require "kong.constants" local route_helpers = require "kong.api.route_helpers" +local utils = require "kong.tools.utils" return { ["/"] = { @@ -12,7 +13,7 @@ return { return helpers.responses.send_HTTP_OK({ tagline = "Welcome to Kong", version = constants.VERSION, - hostname = route_helpers.get_hostname(), + hostname = utils.get_hostname(), plugins = { available_on_server = configuration.plugins_available, enabled_in_cluster = db_plugins diff --git a/kong/cli/cmds/cluster.lua b/kong/cli/cmds/cluster.lua new file mode 100644 index 00000000000..89aeb8a6f53 --- /dev/null +++ b/kong/cli/cmds/cluster.lua @@ -0,0 +1,52 @@ +#!/usr/bin/env lua + +local constants = require "kong.constants" +local logger = require "kong.cli.utils.logger" +local utils = require "kong.tools.utils" +local configuration = require "kong.cli.utils.configuration" +local serf = require "kong.cli.services.serf" +local args = require("lapp")(string.format([[ +Kong cluster operations. + +Usage: kong cluster [options] + +Commands: + (string) where is one of: + join, leave, force-leave, members, keygen + +Options: + -c,--config (default %s) path to configuration file + +]], constants.CLI.GLOBAL_KONG_CONF)) + +local JOIN = "join" +local SUPPORTED_COMMANDS = { JOIN, "members", "keygen" } + +if not utils.table_contains(SUPPORTED_COMMANDS, args.command) then + logger:error("Invalid cluster command. Supported commands are: "..table.concat(SUPPORTED_COMMANDS, ", ")) + os.exit(1) +end + +local config, err = configuration.parse(args.config) +if err then + logger:error(err) + os.exit(1) +end + +local signal = args.command +args.command = nil +args.config = nil + +if signal == JOIN then + if utils.table_size(args) ~= 1 then + logger:error("You must specify one address") + os.exit(1) + end +end + +local res, err = serf(config.value):invoke_signal(signal, args) +if err then + logger:error(err) +else + logger:print(res) +end \ No newline at end of file diff --git a/kong/cli/config.lua b/kong/cli/cmds/config.lua similarity index 82% rename from kong/cli/config.lua rename to kong/cli/cmds/config.lua index 6acf77a7ade..8d446626964 100644 --- a/kong/cli/config.lua +++ b/kong/cli/cmds/config.lua @@ -1,9 +1,10 @@ #!/usr/bin/env lua local constants = require "kong.constants" -local cutils = require "kong.cli.utils" +local logger = require "kong.cli.utils.logger" local IO = require "kong.tools.io" local yaml = require "yaml" +local configuration = require "kong.cli.utils.configuration" local args = require("lapp")(string.format([[ For development purposes only. @@ -19,9 +20,7 @@ Options: local CONFIG_FILENAME = string.format("kong%s.yml", args.env ~= "" and "_"..args.env or "") -local config_path = cutils.get_kong_config_path(args.config) -local config_content = IO.read_file(config_path) -local default_config = yaml.load(config_content) +local parsed_config = configuration.parse(args.config).value local env = args.env:upper() local DEFAULT_ENV_VALUES = { @@ -33,6 +32,12 @@ local DEFAULT_ENV_VALUES = { ["proxy_ssl_port"] = 8543, ["admin_api_port"] = 8101, ["dnsmasq_port"] = 8153, + -- TODO: This values are not properly dumped in enclosing quotes + -- which generates a wrong YAML value + --["cluster"] = { + -- ["bind"] = "0.0.0.0:7947", + -- ["rpc-addr"] = "127.0.0.1:7374" + --}, ["databases_available"] = { ["cassandra"] = { ["keyspace"] = "kong_tests" @@ -65,7 +70,8 @@ local DEFAULT_ENV_VALUES = { } if not DEFAULT_ENV_VALUES[args.env:upper()] then - cutils.error_exit(string.format("Unregistered environment '%s'", args.env:upper())) + logger:error(string.format("Unregistered environment '%s'", args.env:upper())) + os.exit(1) end -- Create the new configuration as a new blank object @@ -80,7 +86,7 @@ end local new_config_content = yaml.dump(new_config) -- Replace nginx directives -local nginx_config = default_config.nginx +local nginx_config = parsed_config.nginx for k, v in pairs(DEFAULT_ENV_VALUES[env].nginx) do nginx_config = nginx_config:gsub(k, v) end @@ -97,5 +103,6 @@ nginx: | local ok, err = IO.write_to_file(IO.path:join(args.output, CONFIG_FILENAME), new_config_content) if not ok then - cutils.error_exit(err) + logger:error(err) + os.exit(1) end diff --git a/kong/cli/db.lua b/kong/cli/cmds/db.lua similarity index 75% rename from kong/cli/db.lua rename to kong/cli/cmds/db.lua index d24a195a603..1e184070725 100644 --- a/kong/cli/db.lua +++ b/kong/cli/cmds/db.lua @@ -2,8 +2,7 @@ local Faker = require "kong.tools.faker" local constants = require "kong.constants" -local cutils = require "kong.cli.utils" -local config = require "kong.tools.config_loader" +local logger = require "kong.cli.utils.logger" local dao = require "kong.tools.dao_loader" local lapp = require("lapp") @@ -29,30 +28,31 @@ if args.command == "db" then lapp.quit("Missing required .") end -local config_path = cutils.get_kong_config_path(args.config) -local config = config.load(config_path) -local dao_factory = dao.load(config) +local parsed_config = require("kong.cli.services.nginx")(args.config).parsed_config +local dao_factory = dao.load(parsed_config) if args.command == "seed" then -- Drop if exists local err = dao_factory:drop() if err then - cutils.logger:error_exit(err) + logger:error(err) + os.exit(1) end local faker = Faker(dao_factory) faker:seed(args.random and args.number or nil) - cutils.logger:success("Populated") + logger:success("Populated") elseif args.command == "drop" then local err = dao_factory:drop() if err then - cutils.logger:error_exit(err) + logger:error(err) + os.exit(1) end - cutils.logger:success("Dropped") + logger:success("Dropped") else lapp.quit("Invalid command: "..args.command) diff --git a/kong/cli/migrations.lua b/kong/cli/cmds/migrations.lua similarity index 61% rename from kong/cli/migrations.lua rename to kong/cli/cmds/migrations.lua index 71e013fcf95..90be3f50eb2 100644 --- a/kong/cli/migrations.lua +++ b/kong/cli/cmds/migrations.lua @@ -2,10 +2,10 @@ local Migrations = require "kong.tools.migrations" local constants = require "kong.constants" -local cutils = require "kong.cli.utils" +local logger = require "kong.cli.utils.logger" local utils = require "kong.tools.utils" local input = require "kong.cli.utils.input" -local config = require "kong.tools.config_loader" +local configuration = require "kong.cli.utils.configuration" local dao = require "kong.tools.dao_loader" local lapp = require "lapp" local args = lapp(string.format([[ @@ -28,16 +28,16 @@ if args.command == "migrations" then lapp.quit("Missing required .") end -local config_path = cutils.get_kong_config_path(args.config) -local configuration = config.load(config_path) -local dao_factory = dao.load(configuration) +local parsed_config = configuration.parse(args.config).value +local dao_factory = dao.load(parsed_config) local migrations = Migrations(dao_factory) local kind = args.type if kind ~= "all" and kind ~= "core" then -- Assuming we are trying to run migrations for a plugin - if not utils.table_contains(configuration.plugins_available, kind) then - cutils.logger:error_exit("No \""..kind.."\" plugin enabled in the configuration.") + if not utils.table_contains(parsed_config.plugins_available, kind) then + logger:error("No \""..kind.."\" plugin enabled in the configuration.") + os.exit(1) end end @@ -45,55 +45,57 @@ if args.command == "list" then local migrations, err = dao_factory.migrations:get_migrations() if err then - cutils.logger:error_exit(err) + logger:error(err) + os.exit(1) elseif migrations then - cutils.logger:info(string.format( + logger:info(string.format( "Executed migrations for keyspace %s (%s):", - cutils.colors.yellow(dao_factory._properties.keyspace), + logger.colors.yellow(dao_factory._properties.keyspace), dao_factory.type )) for _, row in ipairs(migrations) do - cutils.logger:info(string.format("%s: %s", - cutils.colors.yellow(row.id), + logger:info(string.format("%s: %s", + logger.colors.yellow(row.id), table.concat(row.migrations, ", ") )) end else - cutils.logger:info(string.format( + logger:info(string.format( "No migrations have been run yet for %s on keyspace: %s", - cutils.colors.yellow(dao_factory.type), - cutils.colors.yellow(dao_factory._properties.keyspace) + logger.colors.yellow(dao_factory.type), + logger.colors.yellow(dao_factory._properties.keyspace) )) end elseif args.command == "up" then local function migrate(identifier) - cutils.logger:info(string.format( + logger:info(string.format( "Migrating %s on keyspace \"%s\" (%s)", - cutils.colors.yellow(identifier), - cutils.colors.yellow(dao_factory._properties.keyspace), + logger.colors.yellow(identifier), + logger.colors.yellow(dao_factory._properties.keyspace), dao_factory.type )) local err = migrations:migrate(identifier, function(identifier, migration) if migration then - cutils.logger:info(string.format( + logger:info(string.format( "%s migrated up to: %s", identifier, - cutils.colors.yellow(migration.name) + logger.colors.yellow(migration.name) )) end end) if err then - cutils.logger:error_exit(err) + logger:error(err) + os.exit(1) end end if kind == "all" then migrate("core") - for _, plugin_name in ipairs(configuration.plugins_available) do + for _, plugin_name in ipairs(parsed_config.plugins_available) do local has_migrations = utils.load_module_if_exists("kong.plugins."..plugin_name..".migrations."..dao_factory.type) if has_migrations then migrate(plugin_name) @@ -103,46 +105,49 @@ elseif args.command == "up" then migrate(kind) end - cutils.logger:success("Schema up to date") + logger:success("Schema up to date") elseif args.command == "down" then if kind == "all" then - cutils.logger:error_exit("You must specify 'core' or a plugin name for this command.") + logger:error("You must specify 'core' or a plugin name for this command.") + os.exit(1) end - cutils.logger:info(string.format( + logger:info(string.format( "Rollbacking %s in keyspace \"%s\" (%s)", - cutils.colors.yellow(kind), - cutils.colors.yellow(dao_factory._properties.keyspace), + logger.colors.yellow(kind), + logger.colors.yellow(dao_factory._properties.keyspace), dao_factory.type )) local rollbacked, err = migrations:rollback(kind) if err then - cutils.logger:error_exit(err) + logger:error(err) + os.exit(1) elseif rollbacked then - cutils.logger:success("\""..kind.."\" rollbacked: "..cutils.colors.yellow(rollbacked.name)) + logger:success("\""..kind.."\" rollbacked: "..logger.colors.yellow(rollbacked.name)) else - cutils.logger:success("No migration to rollback") + logger:success("No migration to rollback") end elseif args.command == "reset" then local keyspace = dao_factory._properties.keyspace - cutils.logger:info(string.format( + logger:info(string.format( "Resetting \"%s\" keyspace (%s)", - cutils.colors.yellow(keyspace), + logger.colors.yellow(keyspace), dao_factory.type )) if input.confirm("Are you sure? You will lose all of your data, this operation is irreversible.") then local _, err = dao_factory.migrations:drop_keyspace(keyspace) if err then - cutils.logger:error_exit(err) + logger:error(err) + os.exit(1) else - cutils.logger:success("Keyspace successfully reset") + logger:success("Keyspace successfully reset") end end else diff --git a/kong/cli/cmds/quit.lua b/kong/cli/cmds/quit.lua new file mode 100644 index 00000000000..b537da7973b --- /dev/null +++ b/kong/cli/cmds/quit.lua @@ -0,0 +1,35 @@ +#!/usr/bin/env lua + +local constants = require "kong.constants" +local logger = require "kong.cli.utils.logger" +local configuration = require "kong.cli.utils.configuration" +local services = require "kong.cli.utils.services" +local args = require("lapp")(string.format([[ +Graceful shutdown. Stop the Kong instance running in the configured 'nginx_working_dir' directory. + +Usage: kong stop [options] + +Options: + -c,--config (default %s) path to configuration file +]], constants.CLI.GLOBAL_KONG_CONF)) + +local config, err = configuration.parse(args.config) +if err then + logger:error(err) + os.exit(1) +end + +local nginx = require("kong.cli.services.nginx")(config.value, config.path) + +if not nginx:is_running() then + logger:error("Kong is not running") + os.exit(1) +end + +nginx:quit() +while(nginx:is_running()) do + -- Wait until it quits +end + +services.stop_all(config) +logger:success("Stopped") \ No newline at end of file diff --git a/kong/cli/cmds/reload.lua b/kong/cli/cmds/reload.lua new file mode 100644 index 00000000000..457483d4485 --- /dev/null +++ b/kong/cli/cmds/reload.lua @@ -0,0 +1,36 @@ +#!/usr/bin/env lua + +local constants = require "kong.constants" +local logger = require "kong.cli.utils.logger" +local configuration = require "kong.cli.utils.configuration" +local args = require("lapp")(string.format([[ +Gracefully reload the Kong instance running in the configured 'nginx_working_dir'. + +Any configuration change will be applied. + +Usage: kong reload [options] + +Options: + -c,--config (default %s) path to configuration file +]], constants.CLI.GLOBAL_KONG_CONF)) + +local config, err = configuration.parse(args.config) +if err then + logger:error(err) + os.exit(1) +end + +local nginx = require("kong.cli.services.nginx")(config.value, config.path) + +if not nginx:is_running() then + logger:error("Kong is not running") + os.exit(1) +end + +local _, err = nginx:reload() +if err then + logger:error(err) + os.exit(1) +else + logger:success("Reloaded") +end \ No newline at end of file diff --git a/kong/cli/cmds/restart.lua b/kong/cli/cmds/restart.lua new file mode 100644 index 00000000000..85332290aba --- /dev/null +++ b/kong/cli/cmds/restart.lua @@ -0,0 +1,17 @@ +#!/usr/bin/env lua + +local constants = require "kong.constants" +require("lapp")(string.format([[ +Restart the Kong instance running in the configured 'nginx_working_dir'. + +Kong will be shutdown before restarting. For a zero-downtime reload +of your configuration, look at 'kong reload'. + +Usage: kong restart [options] + +Options: + -c,--config (default %s) path to configuration file +]], constants.CLI.GLOBAL_KONG_CONF)) + +require("kong.cli.cmds.stop") +require("kong.cli.cmds.start") \ No newline at end of file diff --git a/kong/cli/cmds/start.lua b/kong/cli/cmds/start.lua new file mode 100755 index 00000000000..0296ad8a4a2 --- /dev/null +++ b/kong/cli/cmds/start.lua @@ -0,0 +1,32 @@ +#!/usr/bin/env lua + +local constants = require "kong.constants" +local configuration = require "kong.cli.utils.configuration" +local logger = require "kong.cli.utils.logger" +local services = require "kong.cli.utils.services" + +local args = require("lapp")(string.format([[ +Start Kong with given configuration. Kong will run in the configured 'nginx_working_dir' directory. + +Usage: kong start [options] + +Options: + -c,--config (default %s) path to configuration file +]], constants.CLI.GLOBAL_KONG_CONF)) + +logger:info("Kong "..constants.VERSION) + +local config, err = configuration.parse(args.config) +if err then + logger:error(err) + os.exit(1) +end + +local ok, err = services.start_all(config) +if ok then + logger:success("Started") +else + services.stop_all(config) + logger:error(err) + os.exit(1) +end \ No newline at end of file diff --git a/kong/cli/cmds/stop.lua b/kong/cli/cmds/stop.lua new file mode 100755 index 00000000000..b30cf5791b2 --- /dev/null +++ b/kong/cli/cmds/stop.lua @@ -0,0 +1,24 @@ +#!/usr/bin/env lua + +local constants = require "kong.constants" +local logger = require "kong.cli.utils.logger" +local services = require "kong.cli.utils.services" +local configuration = require "kong.cli.utils.configuration" +local args = require("lapp")(string.format([[ +Fast shutdown. Stop the Kong instance running in the configured 'nginx_working_dir' directory. + +Usage: kong stop [options] + +Options: + -c,--config (default %s) path to configuration file +]], constants.CLI.GLOBAL_KONG_CONF)) + +local config, err = configuration.parse(args.config) +if err then + logger:error(err) + os.exit(1) +end + +services.stop_all(config) + +logger:success("Stopped") \ No newline at end of file diff --git a/kong/cli/cmds/version.lua b/kong/cli/cmds/version.lua new file mode 100644 index 00000000000..f577c525458 --- /dev/null +++ b/kong/cli/cmds/version.lua @@ -0,0 +1,6 @@ +#!/usr/bin/env lua + +local logger = require "kong.cli.utils.logger" +local constants = require "kong.constants" + +logger:print(string.format("Kong version: %s", constants.VERSION)) diff --git a/kong/cli/quit.lua b/kong/cli/quit.lua deleted file mode 100644 index 9393e80d58b..00000000000 --- a/kong/cli/quit.lua +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env lua - -local constants = require "kong.constants" -local cutils = require "kong.cli.utils" -local signal = require "kong.cli.utils.signal" -local args = require("lapp")(string.format([[ -Graceful shutdown. Stop the Kong instance running in the configured 'nginx_working_dir' directory. - -Usage: kong stop [options] - -Options: - -c,--config (default %s) path to configuration file -]], constants.CLI.GLOBAL_KONG_CONF)) - --- Check if running, will exit if not -local running, err = signal.is_running(args.config) -if not running then - cutils.logger:error_exit(err) -end - --- Send 'quit' signal (graceful shutdown) -if signal.send_signal(args.config, signal.QUIT) then - cutils.logger:success("Stopped") -else - cutils.logger:error_exit("Could not gracefully stop Kong") -end diff --git a/kong/cli/reload.lua b/kong/cli/reload.lua deleted file mode 100644 index 21c10d8159e..00000000000 --- a/kong/cli/reload.lua +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env lua - -local constants = require "kong.constants" -local cutils = require "kong.cli.utils" -local signal = require "kong.cli.utils.signal" -local args = require("lapp")(string.format([[ -Gracefully reload the Kong instance running in the configured 'nginx_working_dir'. - -Any configuration change will be applied. - -Usage: kong reload [options] - -Options: - -c,--config (default %s) path to configuration file -]], constants.CLI.GLOBAL_KONG_CONF)) - -if not signal.is_running(args.config) then - cutils.logger:error_exit("Could not reload: Kong is not running.") -end - -signal.prepare_kong(args.config, signal.RELOAD) - -if signal.send_signal(args.config, signal.RELOAD) then - cutils.logger:success("Reloaded") -else - cutils.logger:error_exit("Could not reload Kong") -end diff --git a/kong/cli/restart.lua b/kong/cli/restart.lua deleted file mode 100644 index c347cb3c638..00000000000 --- a/kong/cli/restart.lua +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env lua - -local constants = require "kong.constants" -local cutils = require "kong.cli.utils" -local signal = require "kong.cli.utils.signal" -local args = require("lapp")(string.format([[ -Restart the Kong instance running in the configured 'nginx_working_dir'. - -Kong will be shutdown before restarting. For a zero-downtime reload -of your configuration, look at 'kong reload'. - -Usage: kong restart [options] - -Options: - -c,--config (default %s) path to configuration file -]], constants.CLI.GLOBAL_KONG_CONF)) - -if signal.is_running(args.config) then - if not signal.send_signal(args.config, signal.STOP) then - cutils.logger:error_exit("Could not stop Kong") - end -end - -signal.prepare_kong(args.config) - -if not signal.send_signal(args.config) then - cutils.logger:error_exit("Could not restart Kong") -end - -cutils.logger:success("Restarted") diff --git a/kong/cli/services/base_service.lua b/kong/cli/services/base_service.lua new file mode 100644 index 00000000000..798f77bba9f --- /dev/null +++ b/kong/cli/services/base_service.lua @@ -0,0 +1,94 @@ +local Object = require "classic" +local IO = require "kong.tools.io" +local stringy = require "stringy" +local utils = require "kong.tools.utils" + +local BaseService = Object:extend() + +function BaseService.find_cmd(app_name, additional_paths, check_path_func) + local found_file_paths = {} + + if IO.cmd_exists(app_name) and not check_path_func then + return app_name + elseif IO.cmd_exists(app_name) then + table.insert(found_file_paths, app_name) + end + + -- These are some default locations we are always looking into + local search_dirs = utils.table_merge({ + "/usr/local/sbin", + "/usr/sbin", + "/usr/bin", + "/bin" + }, additional_paths and additional_paths or {}) + + for _, search_dir in ipairs(search_dirs) do + local file_path = search_dir..(stringy.endswith(search_dir, "/") and "" or "/")..app_name + if IO.file_exists(file_path) then + table.insert(found_file_paths, file_path) + end + end + + if check_path_func then + for _, found_file_path in ipairs(found_file_paths) do + if check_path_func(found_file_path) then + return found_file_path + end + end + elseif #found_file_paths > 0 then + -- Just return the first path + return table.remove(found_file_paths, 1) + end + + return nil +end + +function BaseService:new(name, nginx_working_dir) + self._name = name + self._pid_file_path = nginx_working_dir + ..(stringy.endswith(nginx_working_dir, "/") and "" or "/") + ..name..".pid" +end + +function BaseService:is_running() + local result = nil + + local pid = IO.read_file(self._pid_file_path) + if pid then + local _, code = IO.os_execute("ps -p "..stringy.strip(pid)) + if code and code == 0 then + result = pid + end + end + + return result +end + +function BaseService:_get_cmd(additional_paths, check_path_func) + local cmd = BaseService.find_cmd(self._name, additional_paths, check_path_func) + if not cmd then + return nil, "Can't find "..self._name + end + return cmd +end + +function BaseService:start() + -- Returns an error if not implemented + error("Not implemented") +end + +function BaseService:prepare() + -- Does nothing if not being overridden +end + +function BaseService:stop() + local pid = self:is_running() + if pid then + IO.os_execute("kill "..pid) + while self:is_running() do + -- Wait + end + end +end + +return BaseService \ No newline at end of file diff --git a/kong/cli/services/dnsmasq.lua b/kong/cli/services/dnsmasq.lua new file mode 100644 index 00000000000..4b8a01e2432 --- /dev/null +++ b/kong/cli/services/dnsmasq.lua @@ -0,0 +1,42 @@ +local BaseService = require "kong.cli.services.base_service" +local logger = require "kong.cli.utils.logger" +local IO = require "kong.tools.io" + +local Dnsmasq = BaseService:extend() + +local SERVICE_NAME = "dnsmasq" + +function Dnsmasq:new(configuration_value) + self._parsed_config = configuration_value + Dnsmasq.super.new(self, SERVICE_NAME, configuration_value.nginx_working_dir) +end + +function Dnsmasq:start() + if self._parsed_config.dns_resolver.dnsmasq then + if self:is_running() then + return nil, SERVICE_NAME.." is already running" + end + + local cmd, err = Dnsmasq.super._get_cmd(self) + if err then + return nil, err + end + + local res, code = IO.os_execute(cmd.." -p "..self._parsed_config.dns_resolver.port.." --pid-file="..self._pid_file_path.." -N -o") + if code == 0 then + setmetatable(self._parsed_config.dns_resolver, require "kong.tools.printable") + logger:info(string.format([[dnsmasq ...........%s]], tostring(self._parsed_config.dns_resolver))) + return true + else + return nil, res + end + end +end + +function Dnsmasq:stop() + if self._parsed_config.dns_resolver.dnsmasq then + Dnsmasq.super.stop(self) + end +end + +return Dnsmasq \ No newline at end of file diff --git a/kong/cli/services/nginx.lua b/kong/cli/services/nginx.lua new file mode 100644 index 00000000000..e6e9267b5a1 --- /dev/null +++ b/kong/cli/services/nginx.lua @@ -0,0 +1,263 @@ +local BaseService = require "kong.cli.services.base_service" +local IO = require "kong.tools.io" +local logger = require "kong.cli.utils.logger" +local ssl = require "kong.cli.utils.ssl" +local constants = require "kong.constants" +local syslog = require "kong.tools.syslog" +local dao = require "kong.tools.dao_loader" +local socket = require "socket" + +local Nginx = BaseService:extend() + +local SERVICE_NAME = "nginx" +local START = "start" +local RELOAD = "reload" +local STOP = "stop" +local QUIT = "quit" + +local function prepare_folders(parsed_config) + -- Create nginx folder if needed + local _, err = IO.path:mkdir(IO.path:join(parsed_config.nginx_working_dir, "logs")) + if err then + return false, err + end + + -- Create logs files + os.execute("touch "..IO.path:join(parsed_config.nginx_working_dir, "logs", "error.log")) + os.execute("touch "..IO.path:join(parsed_config.nginx_working_dir, "logs", "access.log")) + + -- Create SSL folder if needed + local _, err = IO.path:mkdir(IO.path:join(parsed_config.nginx_working_dir, "ssl")) + if err then + return false, err + end + + return true +end + +local function prepare_ssl_certificates(parsed_config) + local _, err = ssl.prepare_ssl(parsed_config) + if err then + return false, err + end + + local res, err = ssl.get_ssl_cert_and_key(parsed_config) + if err then + return false, err + end + + local trusted_ssl_cert_path = parsed_config.dao_config.ssl_certificate -- DAO ssl cert + + return { ssl_cert_path = res.ssl_cert_path, + ssl_key_path = res.ssl_key_path, + trusted_ssl_cert_path = trusted_ssl_cert_path } +end + +local function prepare_nginx_configuration(parsed_config, ssl_config) + -- Extract nginx config from kong config, replace any needed value + local nginx_config = parsed_config.nginx + local nginx_inject = { + proxy_port = parsed_config.proxy_port, + proxy_ssl_port = parsed_config.proxy_ssl_port, + admin_api_port = parsed_config.admin_api_port, + dns_resolver = parsed_config.dns_resolver.address, + memory_cache_size = parsed_config.memory_cache_size, + ssl_cert = ssl_config.ssl_cert_path, + ssl_key = ssl_config.ssl_key_path, + lua_ssl_trusted_certificate = ssl_config.trusted_ssl_cert_path ~= nil and "lua_ssl_trusted_certificate \""..ssl_config.trusted_ssl_cert_path.."\";" or "" + } + + -- Auto-tune + local res, code = IO.os_execute("ulimit -n") + if code == 0 then + nginx_inject.auto_worker_rlimit_nofile = res + nginx_inject.auto_worker_connections = tonumber(res) > 16384 and 16384 or res + else + return false, "Can't determine ulimit" + end + + -- Inject properties + for k, v in pairs(nginx_inject) do + nginx_config = nginx_config:gsub("{{"..k.."}}", v) + end + + -- Inject anonymous reports + if parsed_config.send_anonymous_reports then + -- If there is no internet connection, disable this feature + if socket.dns.toip(constants.SYSLOG.ADDRESS) then + nginx_config = "error_log syslog:server="..constants.SYSLOG.ADDRESS..":"..tostring(constants.SYSLOG.PORT).." error;\n"..nginx_config + else + logger:warn("The internet connection might not be available, cannot resolve "..constants.SYSLOG.ADDRESS) + end + end + + -- Write nginx config + local ok, err = IO.write_to_file(IO.path:join(parsed_config.nginx_working_dir, constants.CLI.NGINX_CONFIG), nginx_config) + if not ok then + return false, err + end +end + +local function prepare_database(parsed_config) + setmetatable(parsed_config.dao_config, require "kong.tools.printable") + logger:info(string.format([[database...........%s %s]], parsed_config.database, tostring(parsed_config.dao_config))) + + local dao_factory = dao.load(parsed_config) + local migrations = require("kong.tools.migrations")(dao_factory) + + local keyspace_exists, err = dao_factory.migrations:keyspace_exists() + if err then + return false, err + elseif not keyspace_exists then + logger:info("Database not initialized. Running migrations...") + end + + local err = migrations:migrate_all(parsed_config, function(identifier, migration) + if migration then + logger:success(string.format("%s migrated up to: %s", identifier, logger.colors.yellow(migration.name))) + end + end) + if err then + return false, err + end + + return true +end + +function Nginx:new(configuration_value, configuration_path) + self._parsed_config = configuration_value + self._kong_config_path = configuration_path + + Nginx.super.new(self, SERVICE_NAME, self._parsed_config.nginx_working_dir) +end + +function Nginx:prepare() + -- Prepare database + local _, err = prepare_database(self._parsed_config) + if err then + return false, err + end + + -- Preparing nginx folders + local _, err = prepare_folders(self._parsed_config) + if err then + return false, err + end + + -- Preparing SSL certificates + local res, err = prepare_ssl_certificates(self._parsed_config) + if err then + return false, err + end + + -- Preparing the Nginx configuration file + local _, err = prepare_nginx_configuration(self._parsed_config, res) + if err then + return false, err + end + + return true +end + +function Nginx:_invoke_signal(cmd, signal) + local full_nginx_cmd = string.format("KONG_CONF=%s %s -p %s -c %s -g 'pid %s;' %s", + self._kong_config_path, + cmd, + self._parsed_config.nginx_working_dir, + constants.CLI.NGINX_CONFIG, + self._pid_file_path, + signal == START and "" or "-s "..signal) + + -- Check ulimit value + if signal == START or signal == RELOAD then + local res, code = IO.os_execute("ulimit -n") + if code == 0 and tonumber(res) < 4096 then + logger:warn("ulimit is currently set to \""..res.."\". For better performance set it to at least \"4096\" using \"ulimit -n\"") + end + end + + -- Report signal action + if self._parsed_config.send_anonymous_reports then + syslog.log({signal=signal}) + end + + -- Start failure handler + local res, code = IO.os_execute(full_nginx_cmd) + if code == 0 then + return true + else + return false, res + end +end + +function Nginx:_get_cmd() + local cmd, err = Nginx.super._get_cmd(self, { + "/usr/local/openresty/nginx/sbin/", + "/usr/local/opt/openresty/bin/", + "/usr/local/bin/", + "/usr/sbin/" + }, function(path) + local res, code = IO.os_execute(path.." -v") + if code == 0 then + return res:match("^nginx version: ngx_openresty/") or + res:match("^nginx version: openresty/") + end + + return false + end) + + return cmd, err +end + +function Nginx:start() + if self:is_running() then + return nil, SERVICE_NAME.." is already running" + end + + local cmd, err = self:_get_cmd() + if err then + return nil, err + end + + local ok, err = self:_invoke_signal(cmd, START) + if ok then + local ports = { + proxy_port = self._parsed_config.proxy_port, + proxy_ssl_port = self._parsed_config.proxy_ssl_port, + admin_api_port = self._parsed_config.admin_api_port + } + setmetatable(ports, require "kong.tools.printable") + logger:info(string.format([[nginx .............%s]], tostring(ports))) + end + + return ok, err +end + +function Nginx:stop() + local cmd, err = self:_get_cmd() + if err then + return nil, err + end + + return self:_invoke_signal(cmd, STOP) +end + +function Nginx:reload() + local cmd, err = self:_get_cmd() + if err then + return nil, err + end + + return self:_invoke_signal(cmd, RELOAD) +end + +function Nginx:quit() + local cmd, err = self:_get_cmd() + if err then + return nil, err + end + + return self:_invoke_signal(cmd, QUIT) +end + +return Nginx \ No newline at end of file diff --git a/kong/cli/services/serf.lua b/kong/cli/services/serf.lua new file mode 100644 index 00000000000..e1c7efbfbe3 --- /dev/null +++ b/kong/cli/services/serf.lua @@ -0,0 +1,155 @@ +local BaseService = require "kong.cli.services.base_service" +local logger = require "kong.cli.utils.logger" +local IO = require "kong.tools.io" +local stringy = require "stringy" +local utils = require "kong.tools.utils" +local cjson = require "cjson" + +local Serf = BaseService:extend() + +local SERVICE_NAME = "serf" +local LOG_FILE = "/tmp/"..SERVICE_NAME..".log" +local START_TIMEOUT = 5 +local EVENT_NAME = "kong" + +function Serf:new(configuration_value) + local nginx_working_dir = configuration_value.nginx_working_dir + + self._parsed_config = configuration_value + self._script_path = nginx_working_dir + ..(stringy.endswith(nginx_working_dir, "/") and "" or "/") + .."serf_event.sh" + Serf.super.new(self, SERVICE_NAME, nginx_working_dir) +end + +function Serf:prepare() + local luajit_path = BaseService.find_cmd("luajit") + + local script = [[ +#!/bin/sh +JSON_TOPIC_RAW=`cat` # Read from stdin + +JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\\/\\\\} # \ +JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\//\\\/} # / +JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\'/\\\'} # ' (not strictly needed ?) +JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\"/\\\"} # " +JSON_TOPIC_RAW=${JSON_TOPIC_RAW// /\\t} # \t (tab) +JSON_TOPIC_RAW=${JSON_TOPIC_RAW// +/\\\n} # \n (newline) +JSON_TOPIC_RAW=${JSON_TOPIC_RAW//^M/\\\r} # \r (carriage return) +JSON_TOPIC_RAW=${JSON_TOPIC_RAW//^L/\\\f} # \f (form feed) +JSON_TOPIC_RAW=${JSON_TOPIC_RAW//^H/\\\b} # \b (backspace) + +COMMAND='require("kong.tools.http_client").post("http://127.0.0.1:]]..self._parsed_config.admin_api_port..[[/cluster/events/", "'${JSON_TOPIC_RAW}'", {["content-type"] = "application/json"})' + +echo $COMMAND | ]]..luajit_path..[[ +]] + local _, err = IO.write_to_file(self._script_path, script) + if err then + return false, err + end + + -- Adding executable permissions + local res, code = IO.os_execute("chmod +x "..self._script_path) + if code ~= 0 then + return false, res + end + + return true +end + +function Serf:start() + if self:is_running() then + return nil, SERVICE_NAME.." is already running" + end + + local cmd, err = Serf.super._get_cmd(self) + if err then + return nil, err + end + + -- Prepare arguments + local cmd_args = {} + setmetatable(cmd_args, require "kong.tools.printable") + for k, v in pairs(self._parsed_config.cluster) do + if stringy.strip(v) ~= "" then + cmd_args["-"..k] = v + end + end + cmd_args["-log-level"] = "err" + cmd_args["-node"] = utils.get_hostname().."_"..self._parsed_config.cluster.bind + cmd_args["-event-handler"] = "user:"..EVENT_NAME.."="..self._script_path + + local res, code = IO.os_execute("nohup "..cmd.." agent "..tostring(cmd_args).." > "..LOG_FILE.." 2>&1 & echo $! > "..self._pid_file_path) + if code == 0 then + + -- Wait for process to start, with a timeout + local start = os.time() + while not (string.match(IO.read_file("/tmp/serf.log"), "running") or (os.time() > start + START_TIMEOUT)) do + -- Wait + end + + if self:is_running() then + logger:info(string.format([[serf ..............%s]], tostring(cmd_args))) + return true + else + -- Get last error message + local parts = stringy.split(IO.read_file(LOG_FILE), "\n") + return nil, "Could not start serf: "..string.gsub(parts[#parts - 1], "==> ", "") + end + else + return nil, res + end +end + +function Serf:invoke_signal(signal, args, no_rpc) + if not self:is_running() then + return nil, SERVICE_NAME.." is not running" + end + + local cmd, err = Serf.super._get_cmd(self) + if err then + return nil, err + end + + -- Prepare arguments + if not args then args = {} end + if not no_rpc then + args["-rpc-addr"] = self._parsed_config.cluster["rpc-addr"] + end + setmetatable(args, require "kong.tools.printable") + + local res, code = IO.os_execute(cmd.." "..signal.." "..tostring(args), true) + if code == 0 then + return res + else + return false, res + end +end + +function Serf:event(t_payload) + local args = { + ["-coalesce"] = false, + ["-rpc-addr"] = self._parsed_config.cluster["rpc-addr"] + } + setmetatable(args, require "kong.tools.printable") + + local encoded_payload = cjson.encode(t_payload) + if string.len(encoded_payload) > 512 then + -- Serf can't send a payload greater than 512 bytes + return false, "Encoded payload is "..string.len(encoded_payload).." and it exceeds the limit of 512 bytes!" + end + + return self:invoke_signal("event "..tostring(args).." kong", {"'"..encoded_payload.."'"}, true) +end + +function Serf:stop() + local _, err = self:invoke_signal("leave") + if err then + return false, err + else + Serf.super.stop(self) + end +end + +return Serf \ No newline at end of file diff --git a/kong/cli/start.lua b/kong/cli/start.lua deleted file mode 100755 index eabbb601fa6..00000000000 --- a/kong/cli/start.lua +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env lua - -local constants = require "kong.constants" -local cutils = require "kong.cli.utils" -local signal = require "kong.cli.utils.signal" -local args = require("lapp")(string.format([[ -Start Kong with given configuration. Kong will run in the configured 'nginx_working_dir' directory. - -Usage: kong start [options] - -Options: - -c,--config (default %s) path to configuration file -]], constants.CLI.GLOBAL_KONG_CONF)) - --- Check if running, will exit if yes -local running = signal.is_running(args.config) -if running then - cutils.logger:error_exit("Could not start Kong because it is already running") -end - -signal.prepare_kong(args.config) - -if signal.send_signal(args.config) then - cutils.logger:success("Started") -else - cutils.logger:error_exit("Could not start Kong") -end diff --git a/kong/cli/stop.lua b/kong/cli/stop.lua deleted file mode 100755 index 2efe38b712b..00000000000 --- a/kong/cli/stop.lua +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env lua - -local constants = require "kong.constants" -local cutils = require "kong.cli.utils" -local signal = require "kong.cli.utils.signal" -local args = require("lapp")(string.format([[ -Fast shutdown. Stop the Kong instance running in the configured 'nginx_working_dir' directory. - -Usage: kong stop [options] - -Options: - -c,--config (default %s) path to configuration file -]], constants.CLI.GLOBAL_KONG_CONF)) - --- Check if running, will exit if not -local running, err = signal.is_running(args.config) -if not running then - cutils.logger:error_exit(err) -end - --- Send 'stop' signal (fast shutdown) -if signal.send_signal(args.config, signal.STOP) then - cutils.logger:success("Stopped") -else - cutils.logger:error_exit("Could not stop Kong") -end diff --git a/kong/cli/utils/configuration.lua b/kong/cli/utils/configuration.lua new file mode 100644 index 00000000000..d4e05bf208c --- /dev/null +++ b/kong/cli/utils/configuration.lua @@ -0,0 +1,26 @@ +local IO = require "kong.tools.io" +local logger = require "kong.cli.utils.logger" +local luarocks = require "kong.cli.utils.luarocks" +local config = require "kong.tools.config_loader" + +local _M = {} + +function _M.parse(kong_config_path) + if not IO.file_exists(kong_config_path) then + logger:warn("No configuration at: "..kong_config_path.." using default config instead.") + kong_config_path = IO.path:join(luarocks.get_config_dir(), "kong.yml") + end + + logger:info("Using configuration: "..kong_config_path) + + if not IO.file_exists(kong_config_path) then + return false, "No configuration at: "..kong_config_path + end + + return { + value = config.load(kong_config_path), + path = kong_config_path + } +end + +return _M \ No newline at end of file diff --git a/kong/cli/utils/dnsmasq.lua b/kong/cli/utils/dnsmasq.lua deleted file mode 100644 index eb6ddfdbd87..00000000000 --- a/kong/cli/utils/dnsmasq.lua +++ /dev/null @@ -1,44 +0,0 @@ -local IO = require "kong.tools.io" -local cutils = require "kong.cli.utils" -local constants = require "kong.constants" -local stringy = require "stringy" - -local _M = {} - -function _M.stop(kong_config) - local pid_file = kong_config.nginx_working_dir.."/"..constants.CLI.DNSMASQ_PID - local _, code = IO.kill_process_by_pid_file(pid_file) - if code and code == 0 then - cutils.logger:info("dnsmasq stopped") - end -end - -function _M.start(nginx_working_dir, dnsmasq_port) - local cmd = IO.cmd_exists("dnsmasq") and "dnsmasq" - - if not cmd then -- Load dnsmasq given the PATH settings - local env_path = (os.getenv("PATH")..":" or "").."/usr/local/sbin:/usr/sbin" -- Also check in default paths - local paths = stringy.split(env_path, ":") - for _, path in ipairs(paths) do - if IO.file_exists(path..(stringy.endswith(path, "/") and "" or "/").."dnsmasq") then - cmd = path.."/dnsmasq" - break - end - end - end - - if not cmd then - cutils.logger:error_exit("Can't find dnsmasq") - end - - -- Start the dnsmasq daemon - local file_pid = nginx_working_dir..(stringy.endswith(nginx_working_dir, "/") and "" or "/")..constants.CLI.DNSMASQ_PID - local res, code = IO.os_execute(cmd.." -p "..dnsmasq_port.." --pid-file="..file_pid.." -N -o") - if code ~= 0 then - cutils.logger:error_exit(res) - else - cutils.logger:info("dnsmasq started ("..cmd..")") - end -end - -return _M \ No newline at end of file diff --git a/kong/cli/utils/logger.lua b/kong/cli/utils/logger.lua new file mode 100644 index 00000000000..edff713d14a --- /dev/null +++ b/kong/cli/utils/logger.lua @@ -0,0 +1,50 @@ +--[[ +Kong CLI logging +--]] + +local ansicolors = require "ansicolors" +local Object = require "classic" +local stringy = require "stringy" + +-- +-- Colors +-- +local colors = {} +for _, v in ipairs({"red", "green", "yellow", "blue"}) do + colors[v] = function(str) return ansicolors("%{"..v.."}"..str.."%{reset}") end +end + +-- +-- Logging +-- +local Logger = Object:extend() + +Logger.colors = colors + +function Logger:new(silent) + self.silent = silent +end + +function Logger:print(str) + if not self.silent then + print(stringy.strip(str)) + end +end + +function Logger:info(str) + self:print(colors.blue("[INFO] ")..str) +end + +function Logger:success(str) + self:print(colors.green("[OK] ")..str) +end + +function Logger:warn(str) + self:print(colors.yellow("[WARN] ")..str) +end + +function Logger:error(str) + self:print(colors.red("[ERR] ")..str) +end + +return Logger() diff --git a/kong/cli/utils/luarocks.lua b/kong/cli/utils/luarocks.lua new file mode 100644 index 00000000000..2f27b7849ae --- /dev/null +++ b/kong/cli/utils/luarocks.lua @@ -0,0 +1,47 @@ +local constants = require "kong.constants" +local lpath = require "luarocks.path" + +local Luarocks = {} + +-- +-- Luarocks +-- +function Luarocks.get_kong_infos() + return { name = constants.NAME, version = constants.ROCK_VERSION } +end + +function Luarocks.get_dir() + local cfg = require "luarocks.cfg" + local search = require "luarocks.search" + local infos = Luarocks.get_kong_infos() + + local tree_map = {} + local results = {} + + for _, tree in ipairs(cfg.rocks_trees) do + local rocks_dir = lpath.rocks_dir(tree) + tree_map[rocks_dir] = tree + search.manifest_search(results, rocks_dir, search.make_query(infos.name:lower(), infos.version)) + end + + local version + for k, _ in pairs(results.kong) do + version = k + end + + return tree_map[results.kong[version][1].repo] +end + +function Luarocks.get_config_dir() + local repo = Luarocks.get_dir() + local infos = Luarocks.get_kong_infos() + return lpath.conf_dir(infos.name:lower(), infos.version, repo) +end + +function Luarocks.get_install_dir() + local repo = Luarocks.get_dir() + local infos = Luarocks.get_kong_infos() + return lpath.install_dir(infos.name:lower(), infos.version, repo) +end + +return Luarocks \ No newline at end of file diff --git a/kong/cli/utils/services.lua b/kong/cli/utils/services.lua new file mode 100644 index 00000000000..e9c1e3b9767 --- /dev/null +++ b/kong/cli/utils/services.lua @@ -0,0 +1,30 @@ +local _M = {} + +-- Services ordered by priority +local services = { + require "kong.cli.services.serf", + require "kong.cli.services.dnsmasq", + require "kong.cli.services.nginx" +} + +function _M.stop_all(configuration) + -- Stop in reverse order to keep dependencies running + for index = #services,1,-1 do + services[index](configuration.value, configuration.path):stop() + end +end + +function _M.start_all(configuration) + for _, v in ipairs(services) do + local obj = v(configuration.value, configuration.path) + obj:prepare() + local ok, err = obj:start() + if not ok then + return ok, err + end + end + + return true +end + +return _M \ No newline at end of file diff --git a/kong/cli/utils/signal.lua b/kong/cli/utils/signal.lua deleted file mode 100644 index b694598cdcd..00000000000 --- a/kong/cli/utils/signal.lua +++ /dev/null @@ -1,328 +0,0 @@ --- Send signals to the `nginx` executable --- Run the necessary so the nginx working dir (prefix) and database are correctly prepared --- @see http://nginx.org/en/docs/beginners_guide.html#control - -local IO = require "kong.tools.io" -local cutils = require "kong.cli.utils" -local ssl = require "kong.cli.utils.ssl" -local constants = require "kong.constants" -local syslog = require "kong.tools.syslog" -local socket = require "socket" -local dnsmasq = require "kong.cli.utils.dnsmasq" -local config = require "kong.tools.config_loader" -local dao = require "kong.tools.dao_loader" - --- Cache config path, parsed config and DAO factory -local kong_config_path, kong_config - --- Retrieve the desired Kong config file, parse it and provides a DAO factory --- Will cache them for future retrieval --- @param args_config Path to the desired configuration (usually from the --config CLI argument) --- @return Parsed desired Kong configuration --- @return Path to desired Kong config --- @return Instanciated DAO factory -local function get_kong_config(args_config) - -- Get configuration from default or given path - if not kong_config_path then - kong_config_path = cutils.get_kong_config_path(args_config) - cutils.logger:info("Using configuration: "..kong_config_path) - end - if not kong_config then - kong_config = config.load(kong_config_path) - end - return kong_config, kong_config_path -end - --- Check if an executable (typically `nginx`) is a distribution of openresty --- @param path_to_check Path to the binary --- @return true or false -local function is_openresty(path_to_check) - local cmd = path_to_check.." -v" - local out = IO.os_execute(cmd) - return out:match("^nginx version: ngx_openresty/") - or out:match("^nginx version: openresty/") - or out:match("^nginx version: nginx/[%w.%s]+%(nginx%-plus%-extras.+%)") -end - --- Preferred paths where to search for an `nginx` executable in priority to the $PATH -local NGINX_BIN = "nginx" -local NGINX_SEARCH_PATHS = { - "/usr/local/openresty/nginx/sbin/", - "/usr/local/opt/openresty/bin/", - "/usr/local/bin/", - "/usr/sbin/", - "" -- to check the $PATH -} - --- Try to find an `nginx` executable in defined paths, or in $PATH --- @return Path to found executable or nil if none was found -local function find_nginx() - for i = 1, #NGINX_SEARCH_PATHS do - local prefix = NGINX_SEARCH_PATHS[i] - local to_check = prefix..NGINX_BIN - if is_openresty(to_check) then - return to_check - end - end -end - --- Prepare the nginx `--prefix` directory (working directory) --- Extract the nginx config from a Kong config file into an `nginx.conf` file --- @param args_config Path to the desired configuration (usually from the --config CLI argument) -local function prepare_nginx_working_dir(args_config) - local kong_config = get_kong_config(args_config) - - -- Create nginx folder if needed - local _, err = IO.path:mkdir(IO.path:join(kong_config.nginx_working_dir, "logs")) - if err then - cutils.logger:error_exit(err) - end - - -- Create logs files - os.execute("touch "..IO.path:join(kong_config.nginx_working_dir, "logs", "error.log")) - os.execute("touch "..IO.path:join(kong_config.nginx_working_dir, "logs", "access.log")) - - -- Create SSL folder if needed - local _, err = IO.path:mkdir(IO.path:join(kong_config.nginx_working_dir, "ssl")) - if err then - cutils.logger:error_exit(err) - end - - ssl.prepare_ssl(kong_config) - local ssl_cert_path, ssl_key_path = ssl.get_ssl_cert_and_key(kong_config) - local trusted_ssl_cert_path = kong_config.dao_config.ssl_certificate -- DAO ssl cert - - -- Extract nginx config from kong config, replace any needed value - local nginx_config = kong_config.nginx - local nginx_inject = { - proxy_port = kong_config.proxy_port, - proxy_ssl_port = kong_config.proxy_ssl_port, - admin_api_port = kong_config.admin_api_port, - dns_resolver = kong_config.dns_resolver.address, - memory_cache_size = kong_config.memory_cache_size, - ssl_cert = ssl_cert_path, - ssl_key = ssl_key_path, - lua_ssl_trusted_certificate = trusted_ssl_cert_path ~= nil and "lua_ssl_trusted_certificate \""..trusted_ssl_cert_path.."\";" or "" - } - - -- Auto-tune - local res, code = IO.os_execute("ulimit -n") - if code == 0 then - nginx_inject.auto_worker_rlimit_nofile = res - nginx_inject.auto_worker_connections = tonumber(res) > 16384 and 16384 or res - else - cutils.logger:error_exit("Can't determine ulimit") - end - - -- Inject properties - for k, v in pairs(nginx_inject) do - nginx_config = nginx_config:gsub("{{"..k.."}}", v) - end - - -- Inject additional configurations - nginx_inject = { - nginx_plus_status = kong_config.nginx_plus_status and "location /status { status; }" or nil - } - - for _, v in pairs(nginx_inject) do - nginx_config = nginx_config:gsub("# {{additional_configuration}}", "# {{additional_configuration}}\n "..v) - end - - -- Inject anonymous reports - if kong_config.send_anonymous_reports then - -- If there is no internet connection, disable this feature - if socket.dns.toip(constants.SYSLOG.ADDRESS) then - nginx_config = "error_log syslog:server="..constants.SYSLOG.ADDRESS..":"..tostring(constants.SYSLOG.PORT).." error;\n"..nginx_config - else - cutils.logger:warn("The internet connection might not be available, cannot resolve "..constants.SYSLOG.ADDRESS) - end - end - - -- Write nginx config - local ok, err = IO.write_to_file(IO.path:join(kong_config.nginx_working_dir, constants.CLI.NGINX_CONFIG), nginx_config) - if not ok then - cutils.logger:error_exit(err) - end -end - --- Prepare the database keyspace if needed (run schema migrations) --- @param args_config Path to the desired configuration (usually from the --config CLI argument) -local function prepare_database(args_config) - local kong_config = get_kong_config(args_config) - local dao_factory = dao.load(kong_config) - local migrations = require("kong.tools.migrations")(dao_factory) - - local keyspace_exists, err = dao_factory.migrations:keyspace_exists() - if err then - cutils.logger:error_exit(err) - elseif not keyspace_exists then - cutils.logger:info("Database not initialized. Running migrations...") - end - - local err = migrations:migrate_all(kong_config, function(identifier, migration) - if migration then - cutils.logger:success(string.format("%s migrated up to: %s", identifier, cutils.colors.yellow(migration.name))) - end - end) - if err then - cutils.logger:error_exit(err) - end -end - --- --- PUBLIC --- - -local _M = {} - --- Constants -local START = "start" -local RESTART = "restart" -local RELOAD = "reload" -local STOP = "stop" -local QUIT = "quit" - -_M.RELOAD = RELOAD -_M.STOP = STOP -_M.QUIT = QUIT - -function _M.prepare_kong(args_config, signal) - local kong_config = get_kong_config(args_config) - local dao_config = kong_config.dao_config - - local printable_mt = require "kong.tools.printable" - setmetatable(dao_config, printable_mt) - - -- Print important informations - cutils.logger:info(string.format([[Kong version.......%s - Proxy HTTP port....%s - Proxy HTTPS port...%s - Admin API port.....%s - DNS resolver.......%s - Database...........%s %s - ]], - constants.VERSION, - kong_config.proxy_port, - kong_config.proxy_ssl_port, - kong_config.admin_api_port, - kong_config.dns_resolver.address, - kong_config.database, - tostring(dao_config))) - - cutils.logger:info("Connecting to the database...") - prepare_database(args_config) - prepare_nginx_working_dir(args_config, signal) -end - -local function check_port(port) - if cutils.is_port_open(port) then - cutils.logger:error_exit("Port "..tostring(port).." is already being used by another process.") - end -end - --- Send a signal to `nginx`. No signal will start the process --- This function wraps the control of the `nginx` execution. --- @see http://nginx.org/en/docs/beginners_guide.html#control --- @param args_config Path to the desired configuration (usually from the --config CLI argument) --- @param signal Signal to send. Ignoring this argument will try to start `nginx` --- @return A boolean: true for success, false otherwise -function _M.send_signal(args_config, signal) - -- Make sure nginx is there and is openresty - local nginx_path = find_nginx() - if not nginx_path then - cutils.logger:error_exit(string.format("Kong cannot find an 'nginx' executable.\nMake sure it is in your $PATH or in one of the following directories:\n%s", table.concat(NGINX_SEARCH_PATHS, "\n"))) - end - - local kong_config, kong_config_path = get_kong_config(args_config) - if not signal then signal = START end - - if signal == START then - local ports = { kong_config.proxy_port, kong_config.proxy_ssl_port, kong_config.admin_api_port } - for _,port in ipairs(ports) do - check_port(port) - end - end - - -- Build nginx signal command - local cmd = string.format("KONG_CONF=%s %s -p %s -c %s -g 'pid %s;' %s", - kong_config_path, - nginx_path, - kong_config.nginx_working_dir, - constants.CLI.NGINX_CONFIG, - constants.CLI.NGINX_PID, - signal == START and "" or "-s "..signal) - - -- dnsmasq start/stop - if signal == START then - dnsmasq.stop(kong_config) - if kong_config.dns_resolver.dnsmasq then - local dnsmasq_port = kong_config.dns_resolver.port - check_port(dnsmasq_port) - dnsmasq.start(kong_config.nginx_working_dir, dnsmasq_port) - end - elseif signal == STOP or signal == QUIT then - dnsmasq.stop(kong_config) - end - - -- Check ulimit value - if signal == START or signal == RESTART or signal == RELOAD then - local res, code = IO.os_execute("ulimit -n") - if code == 0 and tonumber(res) < 4096 then - cutils.logger:warn("ulimit is currently set to \""..res.."\". For better performance set it to at least \"4096\" using \"ulimit -n\"") - end - end - - -- Report signal action - if kong_config.send_anonymous_reports then - syslog.log({signal=signal}) - end - - -- Start failure handler - local success = os.execute(cmd) == 0 - - if signal == START and not success then - dnsmasq.stop(kong_config) -- If the start failed, then stop dnsmasq - end - - if signal == STOP and success then - if IO.file_exists(kong_config.pid_file) then - os.execute("while [ -f "..kong_config.pid_file.." ]; do sleep 0.5; done") - end - end - - return success -end - --- Test if Kong is already running by detecting a pid file. --- --- Note: --- If the pid file exists but no process seem to be running, will assume the pid --- is obsolete and try to delete it. --- --- @param args_config Path to the desired configuration (usually from the --config CLI argument) --- @return true is running, false otherwise --- @return If not running, an error containing the path where the pid was supposed to be -function _M.is_running(args_config) - -- Get configuration from default or given path - local kong_config = get_kong_config(args_config) - - if IO.file_exists(kong_config.pid_file) then - local pid = IO.read_file(kong_config.pid_file) - local _, code = IO.os_execute("kill -0 "..pid) - if code == 0 then - return true - else - cutils.logger:warn("It seems like Kong crashed the last time it was started!") - cutils.logger:info("Removing pid at: "..kong_config.pid_file) - local _, err = os.remove(kong_config.pid_file) - if err then - error(err) - end - return false, "Not running. Could not find pid: "..pid - end - else - return false, "Not running. Could not find pid at: "..kong_config.pid_file - end -end - -return _M diff --git a/kong/cli/utils/ssl.lua b/kong/cli/utils/ssl.lua index 880dfe9a665..fdec6deab8f 100644 --- a/kong/cli/utils/ssl.lua +++ b/kong/cli/utils/ssl.lua @@ -1,4 +1,4 @@ -local cutils = require "kong.cli.utils" +local logger = require "kong.cli.utils.logger" local utils = require "kong.tools.utils" local IO = require "kong.tools.io" @@ -9,7 +9,7 @@ function _M.get_ssl_cert_and_key(kong_config) if (kong_config.ssl_cert_path and not kong_config.ssl_key_path) or (kong_config.ssl_key_path and not kong_config.ssl_cert_path) then - cutils.logger:error_exit("Both \"ssl_cert_path\" and \"ssl_key_path\" need to be specified in the configuration, or none of them") + return false, "Both \"ssl_cert_path\" and \"ssl_key_path\" need to be specified in the configuration, or none of them" elseif kong_config.ssl_cert_path and kong_config.ssl_key_path then ssl_cert_path = kong_config.ssl_cert_path ssl_key_path = kong_config.ssl_key_path @@ -20,13 +20,13 @@ function _M.get_ssl_cert_and_key(kong_config) -- Check that the file exists if ssl_cert_path and not IO.file_exists(ssl_cert_path) then - cutils.logger:error_exit("Can't find default Kong SSL certificate at: "..ssl_cert_path) + return false, "Can't find default Kong SSL certificate at: "..ssl_cert_path end if ssl_key_path and not IO.file_exists(ssl_key_path) then - cutils.logger:error_exit("Can't find default Kong SSL key at: "..ssl_key_path) + return false, "Can't find default Kong SSL key at: "..ssl_key_path end - return ssl_cert_path, ssl_key_path + return { ssl_cert_path = ssl_cert_path, ssl_key_path = ssl_key_path } end function _M.prepare_ssl(kong_config) @@ -35,7 +35,7 @@ function _M.prepare_ssl(kong_config) if not (IO.file_exists(ssl_cert_path) and IO.file_exists(ssl_key_path)) then -- Autogenerating the certificates for the first time - cutils.logger:info("Auto-generating the default SSL certificate and key...") + logger:info("Auto-generating the default SSL certificate and key...") local file_name = os.tmpname() local passphrase = utils.random_string() @@ -51,8 +51,10 @@ function _M.prepare_ssl(kong_config) mv ]]..file_name..[[.key ]]..ssl_key_path) if code ~= 0 then - cutils.logger:error_exit("There was an error when auto-generating the default SSL certificate: "..res) + return false, "There was an error when auto-generating the default SSL certificate: "..res end + + return true end end diff --git a/kong/cli/utils/utils.lua b/kong/cli/utils/utils.lua deleted file mode 100644 index 0dd1e7abf39..00000000000 --- a/kong/cli/utils/utils.lua +++ /dev/null @@ -1,137 +0,0 @@ ---[[ -Kong CLI utilities - - Logging - - Luarocks helpers -]] - -local ansicolors = require "ansicolors" -local constants = require "kong.constants" -local Object = require "classic" -local lpath = require "luarocks.path" -local IO = require "kong.tools.io" - --- --- Colors --- -local colors = {} -for _, v in ipairs({"red", "green", "yellow", "blue"}) do - colors[v] = function(str) return ansicolors("%{"..v.."}"..str.."%{reset}") end -end - -local function trim(s) - return (s:gsub("^%s*(.-)%s*$", "%1")) -end - --- --- Logging --- -local Logger = Object:extend() - -function Logger:new(silent) - self.silent = silent -end - -function Logger:print(str) - if not self.silent then - print(trim(str)) - end -end - -function Logger:info(str) - self:print(colors.blue("[INFO] ")..str) -end - -function Logger:success(str) - self:print(colors.green("[OK] ")..str) -end - -function Logger:warn(str) - self:print(colors.yellow("[WARN] ")..str) -end - -function Logger:error(str) - self:print(colors.red("[ERR] ")..str) -end - -function Logger:error_exit(str) - self:error(str) - os.exit(1) -end - -local logger = Logger() - --- --- Luarocks --- -local function get_kong_infos() - return { name = constants.NAME, version = constants.ROCK_VERSION } -end - -local function get_luarocks_dir() - local cfg = require "luarocks.cfg" - local search = require "luarocks.search" - local infos = get_kong_infos() - - local tree_map = {} - local results = {} - - for _, tree in ipairs(cfg.rocks_trees) do - local rocks_dir = lpath.rocks_dir(tree) - tree_map[rocks_dir] = tree - search.manifest_search(results, rocks_dir, search.make_query(infos.name:lower(), infos.version)) - end - - local version - for k, _ in pairs(results.kong) do - version = k - end - - return tree_map[results.kong[version][1].repo] -end - -local function get_luarocks_config_dir() - local repo = get_luarocks_dir() - local infos = get_kong_infos() - return lpath.conf_dir(infos.name:lower(), infos.version, repo) -end - -local function get_luarocks_install_dir() - local repo = get_luarocks_dir() - local infos = get_kong_infos() - return lpath.install_dir(infos.name:lower(), infos.version, repo) -end - -local function get_kong_config_path(args_config) - local config_path = args_config - - -- Use the rock's config if no config at default location - if not IO.file_exists(config_path) then - logger:warn("No configuration at: "..config_path.." using default config instead.") - config_path = IO.path:join(get_luarocks_config_dir(), "kong.yml") - end - - -- Make sure the configuration file really exists - if not IO.file_exists(config_path) then - logger:warn("No configuration at: "..config_path) - logger:error_exit("Could not find a configuration file.") - end - - return config_path -end - --- Checks if a port is open on localhost --- @param `port` The port to check --- @return `open` True if open, false otherwise -local function is_port_open(port) - local _, code = IO.os_execute("nc -w 5 -z 127.0.0.1 "..tostring(port)) - return code == 0 -end - -return { - colors = colors, - logger = logger, - get_kong_infos = get_kong_infos, - get_kong_config_path = get_kong_config_path, - get_luarocks_install_dir = get_luarocks_install_dir, - is_port_open = is_port_open -} diff --git a/kong/cli/version.lua b/kong/cli/version.lua deleted file mode 100644 index 2d39a0afd6f..00000000000 --- a/kong/cli/version.lua +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env lua - -local cutils = require "kong.cli.utils" -local constants = require "kong.constants" - -cutils.logger:print(string.format("Kong version: %s", constants.VERSION)) diff --git a/kong/constants.lua b/kong/constants.lua index 0a78d886121..04805b8a3a7 100644 --- a/kong/constants.lua +++ b/kong/constants.lua @@ -11,9 +11,7 @@ return { }, CLI = { GLOBAL_KONG_CONF = "/etc/kong/kong.yml", - NGINX_CONFIG = "nginx.conf", - NGINX_PID = "kong.pid", - DNSMASQ_PID = "dnsmasq.pid", + NGINX_CONFIG = "nginx.conf" }, DATABASE_NULL_ID = "00000000-0000-0000-0000-000000000000", DATABASE_ERROR_TYPES = setmetatable ({ diff --git a/kong/core/events.lua b/kong/core/events.lua new file mode 100644 index 00000000000..57fc88db8c5 --- /dev/null +++ b/kong/core/events.lua @@ -0,0 +1,30 @@ +local Object = require "classic" +local Mediator = require "mediator" + +local Events = Object:extend() + +Events.TYPES = { + CLUSTER_PROPAGATE = "CLUSTER_PROPAGATE", + ENTITY_CREATED = "ENTITY_CREATED", + ENTITY_UPDATED = "ENTITY_UPDATED", + ENTITY_DELETED = "ENTITY_DELETED" +} + +function Events:new(plugins) + self._mediator = Mediator() +end + +function Events:subscribe(event_name, fn) + if fn then + self._mediator:subscribe({event_name}, function(message_t) + fn(message_t) + return nil, true -- Required to tell mediator to continue processing other events + end) + end +end + +function Events:publish(event_name, message_t) + self._mediator:publish({event_name}, message_t) +end + +return Events \ No newline at end of file diff --git a/kong/core/hooks.lua b/kong/core/hooks.lua new file mode 100644 index 00000000000..0df780c48e1 --- /dev/null +++ b/kong/core/hooks.lua @@ -0,0 +1,39 @@ +local events = require "kong.core.events" +local cache = require "kong.tools.database_cache" + +local function invalidate_plugin(entity) + cache.delete(cache.plugin_key(entity.name, entity.api_id, entity.consumer_id)) +end + +local function invalidate(message_t) + if message_t.collection == "consumers" then + cache.delete(cache.consumer_key(message_t.entity.id)) + elseif message_t.collection == "apis" then + if message_t.entity then + cache.delete(cache.api_key(message_t.entity.id)) + end + cache.delete(cache.all_apis_by_dict_key()) + elseif message_t.collection == "plugins" then + -- Handles both the update and the delete + invalidate_plugin(message_t.old_entity and message_t.old_entity or message_t.entity) + end +end + +return { + [events.TYPES.ENTITY_UPDATED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.ENTITY_DELETED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.ENTITY_CREATED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.CLUSTER_PROPAGATE] = function(message_t) + local serf = require("kong.cli.services.serf")(configuration) + local ok, err = serf:event(message_t) + if not ok then + ngx.log(ngx.ERR, err) + end + end +} \ No newline at end of file diff --git a/kong/core/resolver.lua b/kong/core/resolver.lua index c070acd99ca..3b7a324d93f 100644 --- a/kong/core/resolver.lua +++ b/kong/core/resolver.lua @@ -184,7 +184,7 @@ local function find_api(uri) local api, all_hosts, strip_request_path_pattern -- Retrieve all APIs - local apis_dics, err = cache.get_or_set("ALL_APIS_BY_DIC", _M.load_apis_in_memory, 60) -- 60 seconds cache, longer than usual + local apis_dics, err = cache.get_or_set(cache.all_apis_by_dict_key(), _M.load_apis_in_memory) if err then return err end diff --git a/kong/dao/cassandra/apis.lua b/kong/dao/cassandra/apis.lua index 3933f5f2a33..35400cdc0cd 100644 --- a/kong/dao/cassandra/apis.lua +++ b/kong/dao/cassandra/apis.lua @@ -4,10 +4,10 @@ local query_builder = require "kong.dao.cassandra.query_builder" local Apis = BaseDao:extend() -function Apis:new(properties) +function Apis:new(properties, events_handler) self._table = "apis" self._schema = apis_schema - Apis.super.new(self, properties) + Apis.super.new(self, properties, events_handler) end function Apis:find_all() diff --git a/kong/dao/cassandra/base_dao.lua b/kong/dao/cassandra/base_dao.lua index 9cc5788d845..64c27d8aaeb 100644 --- a/kong/dao/cassandra/base_dao.lua +++ b/kong/dao/cassandra/base_dao.lua @@ -15,6 +15,7 @@ local stringy = require "stringy" local Object = require "classic" local utils = require "kong.tools.utils" local uuid = require "lua_uuid" +local event_types = require("kong.core.events").TYPES local cassandra_constants = cassandra.constants local error_types = constants.DATABASE_ERROR_TYPES @@ -145,7 +146,9 @@ function BaseDao:insert(t) if stmt_err then return nil, stmt_err else - return self:_unmarshall(t) + local res = self:_unmarshall(t) + self:_event(event_types.ENTITY_CREATED, res) + return res end end @@ -200,15 +203,15 @@ function BaseDao:update(t, full) local ok, db_err, errors, self_err -- Check if exists to prevent upsert - local res, err = self:find_by_primary_key(t) + local entity, err = self:find_by_primary_key(t) if err then return false, err - elseif not res then + elseif not entity then return false end if not full then - fix_tables(t, res, self._schema) + fix_tables(t, entity, self._schema) end -- Validate schema @@ -255,7 +258,9 @@ function BaseDao:update(t, full) if stmt_err then return nil, stmt_err else - return self:_unmarshall(t) + local res = self:_unmarshall(t) + self:_event(event_types.ENTITY_UPDATED, entity) + return res end end @@ -335,17 +340,17 @@ end --- -- Delete the row with PRIMARY KEY from the configured table (**_table** attribute). -- @param[table=table] where_t A table containing the PRIMARY KEY (columns/values) of the row to delete --- @treturn boolean True if deleted, false if otherwise or not found +-- @treturn table Returns the deleted entity -- @treturn table Error if any during the query execution or the cascade delete hook function BaseDao:delete(where_t) assert(self._primary_key ~= nil and type(self._primary_key) == "table" , "Entity does not have a primary_key") assert(where_t ~= nil and type(where_t) == "table", "where_t must be a table") -- Test if exists first - local res, err = self:find_by_primary_key(where_t) + local entity, err = self:find_by_primary_key(where_t) if err then return false, err - elseif not res then + elseif not entity then return false end @@ -365,6 +370,8 @@ function BaseDao:delete(where_t) end end + self:_event(event_types.ENTITY_DELETED, entity) + return results end @@ -387,7 +394,7 @@ end -- child class and called once the child class has a schema set. -- @param properties Cassandra properties from the configuration file. -- @treturn table Instanciated DAO. -function BaseDao:new(properties) +function BaseDao:new(properties, events_handler) if self._schema then self._primary_key = self._schema.primary_key self._clustering_key = self._schema.clustering_key @@ -406,6 +413,7 @@ function BaseDao:new(properties) end self._properties = properties + self._events = events_handler self._statements_cache = {} self._cascade_delete_hooks = {} end @@ -735,4 +743,24 @@ function BaseDao:add_delete_hook(foreign_dao_name, foreign_column, parent_column table.insert(self._cascade_delete_hooks, delete_hook) end +-- Publishes an event, if an event handler has been specified. +-- Currently this propagates the events cluster-wide. +-- @param[type=string] type The event type to publish +-- @param[type=table] data_t The payload to publish in the event +function BaseDao:_event(type, data_t) + if self._events then + if self._schema.marshall_event then + data_t = self._schema.marshall_event(self._schema, data_t) + end + + local payload = { + collection = self._table, + type = type, + entity = data_t + } + + self._events:publish(self._events.TYPES.CLUSTER_PROPAGATE, payload) + end +end + return BaseDao diff --git a/kong/dao/cassandra/consumers.lua b/kong/dao/cassandra/consumers.lua index 9eda80991ab..e3143233193 100644 --- a/kong/dao/cassandra/consumers.lua +++ b/kong/dao/cassandra/consumers.lua @@ -3,11 +3,11 @@ local consumers_schema = require "kong.dao.schemas.consumers" local Consumers = BaseDao:extend() -function Consumers:new(properties) +function Consumers:new(properties, events_handler) self._table = "consumers" self._schema = consumers_schema - Consumers.super.new(self, properties) + Consumers.super.new(self, properties, events_handler) end return {consumers = Consumers} diff --git a/kong/dao/cassandra/factory.lua b/kong/dao/cassandra/factory.lua index 933c3a00a91..41d91dcbc26 100644 --- a/kong/dao/cassandra/factory.lua +++ b/kong/dao/cassandra/factory.lua @@ -24,8 +24,9 @@ end -- Instantiate a Cassandra Factory and all its DAOs for various entities -- @param `properties` Cassandra properties -function CassandraFactory:new(properties, plugins) +function CassandraFactory:new(properties, plugins, events_handler) self._properties = properties + self._events_handler = events_handler self.type = "cassandra" self.daos = {} @@ -63,7 +64,7 @@ end function CassandraFactory:load_daos(plugin_daos) local dao for name, plugin_dao in pairs(plugin_daos) do - dao = plugin_dao(self._properties) + dao = plugin_dao(self._properties, self._events_handler) dao._factory = self self.daos[name] = dao if dao._schema then diff --git a/kong/dao/cassandra/migrations.lua b/kong/dao/cassandra/migrations.lua index 49c50b78e78..13652f8319f 100644 --- a/kong/dao/cassandra/migrations.lua +++ b/kong/dao/cassandra/migrations.lua @@ -4,7 +4,7 @@ local BaseDao = require "kong.dao.cassandra.base_dao" local Migrations = BaseDao:extend() -function Migrations:new(properties) +function Migrations:new(properties, events_handler) self._table = "schema_migrations" self.queries = { get_keyspace = [[ @@ -24,7 +24,7 @@ function Migrations:new(properties) ]] } - Migrations.super.new(self, properties) + Migrations.super.new(self, properties, events_handler) end function Migrations:keyspace_exists(keyspace) diff --git a/kong/dao/cassandra/plugins.lua b/kong/dao/cassandra/plugins.lua index 161a4fd1332..6909f736742 100644 --- a/kong/dao/cassandra/plugins.lua +++ b/kong/dao/cassandra/plugins.lua @@ -6,11 +6,11 @@ local cjson = require "cjson" local Plugins = BaseDao:extend() -function Plugins:new(properties) +function Plugins:new(properties, events_handler) self._table = "plugins" self._schema = plugins_schema - Plugins.super.new(self, properties) + Plugins.super.new(self, properties, events_handler) end -- @override diff --git a/kong/dao/schemas/plugins.lua b/kong/dao/schemas/plugins.lua index b292955d411..1978256552c 100644 --- a/kong/dao/schemas/plugins.lua +++ b/kong/dao/schemas/plugins.lua @@ -47,6 +47,20 @@ return { type = "boolean", default = true } }, + marshall_event = function(self, plugin_t) + if plugin_t and plugin_t.config then + local config_schema, err = self.fields.config.schema(plugin_t) + if err then + return false, DaoError(err, constants.DATABASE_ERROR_TYPES.SCHEMA) + end + + if config_schema.marshall_event and type(config_schema.marshall_event) == "function" then + plugin_t.config = config_schema.marshall_event(plugin_t.config) + end + end + + return plugin_t + end, self_check = function(self, plugin_t, dao, is_update) -- Load the config schema local config_schema, err = self.fields.config.schema(plugin_t) diff --git a/kong/kong.lua b/kong/kong.lua index 7df64aee17d..740de45a0fc 100644 --- a/kong/kong.lua +++ b/kong/kong.lua @@ -29,6 +29,7 @@ local utils = require "kong.tools.utils" local dao_loader = require "kong.tools.dao_loader" local config_loader = require "kong.tools.config_loader" local plugins_iterator = require "kong.core.plugins_iterator" +local Events = require "kong.core.events" local ipairs = ipairs local table_insert = table.insert @@ -89,6 +90,24 @@ local function load_node_plugins(configuration) return sorted_plugins end +--- Attach a hooks table to the event bus +local function attach_hooks(events, hooks) + for k, v in pairs(hooks) do + events:subscribe(k, v) + end +end + +--- Attach plugin hooks to event bus +-- Subscribe every event in the hooks.lua for every plugin to the event bus +local function attach_plugins_hooks(events, plugins) + for _, plugin in ipairs(plugins) do + local loaded, plugin_hooks = utils.load_module_if_exists("kong.plugins."..plugin.name..".hooks") + if loaded then + attach_hooks(events, plugin_hooks) + end + end +end + --- Kong public context handlers. -- @section kong_handlers @@ -107,8 +126,16 @@ local Kong = {} -- it will be thrown and needs to be catched in `init_by_lua`. function Kong.init() configuration = config_loader.load(os.getenv("KONG_CONF")) - dao = dao_loader.load(configuration) + events = Events() + dao = dao_loader.load(configuration, events) loaded_plugins = load_node_plugins(configuration) + + -- Attach hooks for every plugin + attach_plugins_hooks(events, loaded_plugins) + + -- Attach core hook + attach_hooks(events, require("kong.core.hooks")) + ngx.update_time() end diff --git a/kong/plugins/acl/api.lua b/kong/plugins/acl/api.lua index 4dbd1b573f6..ceaa588650a 100644 --- a/kong/plugins/acl/api.lua +++ b/kong/plugins/acl/api.lua @@ -48,4 +48,4 @@ return { crud.delete(self.acl, dao_factory.acls) end } -} +} \ No newline at end of file diff --git a/kong/plugins/acl/daos.lua b/kong/plugins/acl/daos.lua index a5aaeffa9c2..2938a8d14b8 100644 --- a/kong/plugins/acl/daos.lua +++ b/kong/plugins/acl/daos.lua @@ -1,22 +1,37 @@ local BaseDao = require "kong.dao.cassandra.base_dao" +local function check_unique(group, acl) + -- If dao required to make this work in integration tests when adding fixtures + if dao and acl.consumer_id and group then + local res, err = dao.acls:find_by_keys({consumer_id=acl.consumer_id, group=group}) + if not err and #res > 0 then + return false, "ACL group already exist for this consumer" + elseif not err then + return true + end + end +end + local SCHEMA = { primary_key = {"id"}, fields = { id = { type = "id", dao_insert_value = true }, created_at = { type = "timestamp", dao_insert_value = true }, consumer_id = { type = "id", required = true, foreign = "consumers:id", queryable = true }, - group = { type = "string", required = true } - } + group = { type = "string", required = true, func = check_unique } + }, + marshall_event = function(self, t) + return { id = t.id, consumer_id = t.consumer_id } -- We don't need any data in the event + end } local ACLs = BaseDao:extend() -function ACLs:new(properties) +function ACLs:new(properties, events_handler) self._table = "acls" self._schema = SCHEMA - ACLs.super.new(self, properties) + ACLs.super.new(self, properties, events_handler) end return { acls = ACLs } diff --git a/kong/plugins/acl/hooks.lua b/kong/plugins/acl/hooks.lua new file mode 100644 index 00000000000..3c82b1aaa43 --- /dev/null +++ b/kong/plugins/acl/hooks.lua @@ -0,0 +1,23 @@ +local events = require "kong.core.events" +local cache = require "kong.tools.database_cache" + +local function invalidate_cache(consumer_id) + cache.delete(cache.acls_key(consumer_id)) +end + +local function invalidate(message_t) + if message_t.collection == "consumers" then + invalidate_cache(message_t.entity.id) + elseif message_t.collection == "acls" then + invalidate_cache(message_t.entity.consumer_id) + end +end + +return { + [events.TYPES.ENTITY_UPDATED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.ENTITY_DELETED] = function(message_t) + invalidate(message_t) + end +} \ No newline at end of file diff --git a/kong/plugins/basic-auth/daos.lua b/kong/plugins/basic-auth/daos.lua index f8d9794e2c8..80444fb61e9 100644 --- a/kong/plugins/basic-auth/daos.lua +++ b/kong/plugins/basic-auth/daos.lua @@ -2,6 +2,18 @@ local BaseDao = require "kong.dao.cassandra.base_dao" local crypto = require "kong.plugins.basic-auth.crypto" local function encrypt_password(password, credential) + -- Don't re-encrypt the password digest on update, if the password hasn't changed + -- This causes a bug when a new password is effectively equal the to previous digest + -- TODO: Better handle this scenario + if credential.id then + if dao then -- Check to make this work with tests + local result = dao.basicauth_credentials:find_by_primary_key({id=credential.id}) + if result and result.password == credential.password then + return true + end + end + end + credential.password = crypto.encrypt(credential) return true end @@ -14,16 +26,19 @@ local SCHEMA = { consumer_id = {type = "id", required = true, queryable = true, foreign = "consumers:id"}, username = {type = "string", required = true, unique = true, queryable = true}, password = {type = "string", func = encrypt_password} - } + }, + marshall_event = function(self, t) + return { id = t.id, consumer_id = t.consumer_id, username = t.username } + end } local BasicAuthCredentials = BaseDao:extend() -function BasicAuthCredentials:new(properties) +function BasicAuthCredentials:new(properties, events_handler) self._table = "basicauth_credentials" self._schema = SCHEMA - BasicAuthCredentials.super.new(self, properties) + BasicAuthCredentials.super.new(self, properties, events_handler) end return {basicauth_credentials = BasicAuthCredentials} diff --git a/kong/plugins/basic-auth/hooks.lua b/kong/plugins/basic-auth/hooks.lua new file mode 100644 index 00000000000..a4a4f44195c --- /dev/null +++ b/kong/plugins/basic-auth/hooks.lua @@ -0,0 +1,17 @@ +local events = require "kong.core.events" +local cache = require "kong.tools.database_cache" + +local function invalidate(message_t) + if message_t.collection == "basicauth_credentials" then + cache.delete(cache.basicauth_credential_key(message_t.old_entity and message_t.old_entity.username or message_t.entity.username)) + end +end + +return { + [events.TYPES.ENTITY_UPDATED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.ENTITY_DELETED] = function(message_t) + invalidate(message_t) + end +} \ No newline at end of file diff --git a/kong/plugins/hmac-auth/access.lua b/kong/plugins/hmac-auth/access.lua index 5bac6ec459d..ce778242a0e 100644 --- a/kong/plugins/hmac-auth/access.lua +++ b/kong/plugins/hmac-auth/access.lua @@ -102,14 +102,10 @@ local function validate_signature(request, hmac_params, headers) end end -local function hmacauth_credential_key(username) - return "hmacauth_credentials/"..username -end - local function load_credential(username) local credential if username then - credential = cache.get_or_set(hmacauth_credential_key(username), function() + credential = cache.get_or_set(cache.hmacauth_credential_key(username), function() local keys, err = dao.hmacauth_credentials:find_by_keys { username = username } local result if err then diff --git a/kong/plugins/hmac-auth/daos.lua b/kong/plugins/hmac-auth/daos.lua index f82f0f9062c..90307598a24 100644 --- a/kong/plugins/hmac-auth/daos.lua +++ b/kong/plugins/hmac-auth/daos.lua @@ -8,15 +8,18 @@ local SCHEMA = { consumer_id = { type = "id", required = true, queryable = true, foreign = "consumers:id" }, username = { type = "string", required = true, unique = true, queryable = true }, secret = { type = "string" } - } + }, + marshall_event = function(self, t) + return { id = t.id, consumer_id = t.consumer_id, username = t.username } + end } local HMACAuthCredentials = BaseDao:extend() -function HMACAuthCredentials:new(properties) +function HMACAuthCredentials:new(properties, events_handler) self._table = "hmacauth_credentials" self._schema = SCHEMA - HMACAuthCredentials.super.new(self, properties) + HMACAuthCredentials.super.new(self, properties, events_handler) end return { hmacauth_credentials = HMACAuthCredentials } diff --git a/kong/plugins/hmac-auth/hooks.lua b/kong/plugins/hmac-auth/hooks.lua new file mode 100644 index 00000000000..e3eaa110f5b --- /dev/null +++ b/kong/plugins/hmac-auth/hooks.lua @@ -0,0 +1,17 @@ +local events = require "kong.core.events" +local cache = require "kong.tools.database_cache" + +local function invalidate(message_t) + if message_t.collection == "hmacauth_credentials" then + cache.delete(cache.hmacauth_credential_key(message_t.old_entity and message_t.old_entity.username or message_t.entity.username)) + end +end + +return { + [events.TYPES.ENTITY_UPDATED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.ENTITY_DELETED] = function(message_t) + invalidate(message_t) + end +} \ No newline at end of file diff --git a/kong/plugins/jwt/access.lua b/kong/plugins/jwt/access.lua index 9921a901888..5c17eadf4d5 100644 --- a/kong/plugins/jwt/access.lua +++ b/kong/plugins/jwt/access.lua @@ -42,10 +42,6 @@ local function retrieve_token(request, conf) end end -local function jwt_secret_cache_key(consumer_id) - return "jwt_secret/"..consumer_id -end - function _M.execute(conf) local token, err = retrieve_token(ngx.req, conf) if err then @@ -70,7 +66,7 @@ function _M.execute(conf) end -- Retrieve the secret - local jwt_secret = cache.get_or_set(jwt_secret_cache_key(jwt_secret_key), function() + local jwt_secret = cache.get_or_set(cache.jwtauth_credential_key(jwt_secret_key), function() local rows, err = dao.jwt_secrets:find_by_keys {key = jwt_secret_key} if err then return responses.send_HTTP_INTERNAL_SERVER_ERROR() diff --git a/kong/plugins/jwt/daos.lua b/kong/plugins/jwt/daos.lua index 7da10793352..73b299bfa86 100644 --- a/kong/plugins/jwt/daos.lua +++ b/kong/plugins/jwt/daos.lua @@ -9,16 +9,19 @@ local SCHEMA = { consumer_id = {type = "id", required = true, queryable = true, foreign = "consumers:id"}, key = {type = "string", unique = true, queryable = true, default = utils.random_string}, secret = {type = "string", unique = true, default = utils.random_string} - } + }, + marshall_event = function(self, t) + return { id = t.id, consumer_id = t.consumer_id, key = t.key } + end } local Jwt = BaseDao:extend() -function Jwt:new(properties) +function Jwt:new(properties, events_handler) self._table = "jwt_secrets" self._schema = SCHEMA - Jwt.super.new(self, properties) + Jwt.super.new(self, properties, events_handler) end return {jwt_secrets = Jwt} diff --git a/kong/plugins/jwt/hooks.lua b/kong/plugins/jwt/hooks.lua new file mode 100644 index 00000000000..05f2c1b7a8a --- /dev/null +++ b/kong/plugins/jwt/hooks.lua @@ -0,0 +1,17 @@ +local events = require "kong.core.events" +local cache = require "kong.tools.database_cache" + +local function invalidate(message_t) + if message_t.collection == "jwt_secrets" then + cache.delete(cache.jwtauth_credential_key(message_t.old_entity and message_t.old_entity.key or message_t.entity.key)) + end +end + +return { + [events.TYPES.ENTITY_UPDATED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.ENTITY_DELETED] = function(message_t) + invalidate(message_t) + end +} \ No newline at end of file diff --git a/kong/plugins/key-auth/daos.lua b/kong/plugins/key-auth/daos.lua index 382547970be..197c85ed0f0 100644 --- a/kong/plugins/key-auth/daos.lua +++ b/kong/plugins/key-auth/daos.lua @@ -16,16 +16,19 @@ local SCHEMA = { created_at = { type = "timestamp", dao_insert_value = true }, consumer_id = { type = "id", required = true, queryable = true, foreign = "consumers:id" }, key = { type = "string", required = false, unique = true, queryable = true, func = generate_if_missing } - } + }, + marshall_event = function(self, t) + return { id = t.id, consumer_id = t.consumer_id, key = t.key } + end } local KeyAuth = BaseDao:extend() -function KeyAuth:new(properties) +function KeyAuth:new(properties, events_handler) self._table = "keyauth_credentials" self._schema = SCHEMA - KeyAuth.super.new(self, properties) + KeyAuth.super.new(self, properties, events_handler) end return { keyauth_credentials = KeyAuth } diff --git a/kong/plugins/key-auth/hooks.lua b/kong/plugins/key-auth/hooks.lua new file mode 100644 index 00000000000..336d02ef6f8 --- /dev/null +++ b/kong/plugins/key-auth/hooks.lua @@ -0,0 +1,17 @@ +local events = require "kong.core.events" +local cache = require "kong.tools.database_cache" + +local function invalidate(message_t) + if message_t.collection == "keyauth_credentials" then + cache.delete(cache.keyauth_credential_key(message_t.old_entity and message_t.old_entity.key or message_t.entity.key)) + end +end + +return { + [events.TYPES.ENTITY_UPDATED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.ENTITY_DELETED] = function(message_t) + invalidate(message_t) + end +} \ No newline at end of file diff --git a/kong/plugins/oauth2/api.lua b/kong/plugins/oauth2/api.lua index 462434145c6..664bb07bbb4 100644 --- a/kong/plugins/oauth2/api.lua +++ b/kong/plugins/oauth2/api.lua @@ -1,6 +1,36 @@ local crud = require "kong.api.crud_helpers" return { + ["/oauth2_tokens/"] = { + GET = function(self, dao_factory, helpers) + crud.paginated_set(self, dao_factory.oauth2_tokens) + end + }, + + ["/oauth2_tokens/:id"] = { + before = function(self, dao_factory, helpers) + local err + self.oauth2_token, err = dao_factory.oauth2_tokens:find_by_primary_key({ id = self.params.id }) + if err then + return helpers.yield_error(err) + elseif not self.oauth2_token then + return helpers.responses.send_HTTP_NOT_FOUND() + end + end, + + GET = function(self, dao_factory, helpers) + return helpers.responses.send_HTTP_OK(self.oauth2_token) + end, + + PATCH = function(self, dao_factory) + crud.patch(self.params, self.oauth2_token, dao_factory.oauth2_tokens) + end, + + DELETE = function(self, dao_factory) + crud.delete(self.oauth2_token, dao_factory.oauth2_tokens) + end + }, + ["/oauth2/"] = { GET = function(self, dao_factory, helpers) crud.paginated_set(self, dao_factory.oauth2_credentials) diff --git a/kong/plugins/oauth2/daos.lua b/kong/plugins/oauth2/daos.lua index 9cb239dedf9..db856f4da14 100644 --- a/kong/plugins/oauth2/daos.lua +++ b/kong/plugins/oauth2/daos.lua @@ -26,7 +26,10 @@ local OAUTH2_CREDENTIALS_SCHEMA = { client_secret = { type = "string", required = false, unique = true, func = generate_if_missing }, redirect_uri = { type = "url", required = true }, created_at = { type = "timestamp", dao_insert_value = true } - } + }, + marshall_event = function(self, t) + return { id = t.id, consumer_id = t.consumer_id, client_id = t.client_id } + end } local OAUTH2_AUTHORIZATION_CODES_SCHEMA = { @@ -47,37 +50,40 @@ local OAUTH2_TOKENS_SCHEMA = { id = { type = "id", dao_insert_value = true }, credential_id = { type = "id", required = true, queryable = true, foreign = "oauth2_credentials:id" }, token_type = { type = "string", required = true, enum = { BEARER }, default = BEARER }, - access_token = { type = "string", required = false, unique = true, queryable = true, immutable = true, func = generate_if_missing }, - refresh_token = { type = "string", required = false, unique = true, queryable = true, immutable = true, func = generate_refresh_token }, + access_token = { type = "string", required = false, unique = true, queryable = true, func = generate_if_missing }, + refresh_token = { type = "string", required = false, unique = true, queryable = true, func = generate_refresh_token }, expires_in = { type = "number", required = true }, authenticated_userid = { type = "string", required = false, queryable = true }, scope = { type = "string" }, created_at = { type = "timestamp", dao_insert_value = true } - } + }, + marshall_event = function(self, t) + return { id = t.id, credential_id = t.credential_id, access_token = t.access_token } + end } local OAuth2Credentials = BaseDao:extend() -function OAuth2Credentials:new(properties) +function OAuth2Credentials:new(properties, events_handler) self._table = "oauth2_credentials" self._schema = OAUTH2_CREDENTIALS_SCHEMA - OAuth2Credentials.super.new(self, properties) + OAuth2Credentials.super.new(self, properties, events_handler) end local OAuth2AuthorizationCodes = BaseDao:extend() -function OAuth2AuthorizationCodes:new(properties) +function OAuth2AuthorizationCodes:new(properties, events_handler) self._table = "oauth2_authorization_codes" self._schema = OAUTH2_AUTHORIZATION_CODES_SCHEMA - OAuth2AuthorizationCodes.super.new(self, properties) + OAuth2AuthorizationCodes.super.new(self, properties, events_handler) end local OAuth2Tokens = BaseDao:extend() -function OAuth2Tokens:new(properties) +function OAuth2Tokens:new(properties, events_handler) self._table = "oauth2_tokens" self._schema = OAUTH2_TOKENS_SCHEMA - OAuth2Tokens.super.new(self, properties) + OAuth2Tokens.super.new(self, properties, events_handler) end return { oauth2_credentials = OAuth2Credentials, oauth2_authorization_codes = OAuth2AuthorizationCodes, oauth2_tokens = OAuth2Tokens } diff --git a/kong/plugins/oauth2/hooks.lua b/kong/plugins/oauth2/hooks.lua new file mode 100644 index 00000000000..9f296166172 --- /dev/null +++ b/kong/plugins/oauth2/hooks.lua @@ -0,0 +1,20 @@ +local events = require "kong.core.events" +local cache = require "kong.tools.database_cache" + +local function invalidate(message_t) + if message_t.collection == "oauth2_credentials" then + cache.delete(cache.oauth2_credential_key(message_t.old_entity and message_t.old_entity.client_id or message_t.entity.client_id)) + cache.delete(cache.oauth2_credential_key(message_t.entity.id)) + elseif message_t.collection == "oauth2_tokens" then + cache.delete(cache.oauth2_token_key(message_t.old_entity and message_t.old_entity.access_token or message_t.entity.access_token)) + end +end + +return { + [events.TYPES.ENTITY_UPDATED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.ENTITY_DELETED] = function(message_t) + invalidate(message_t) + end +} \ No newline at end of file diff --git a/kong/plugins/rate-limiting/daos.lua b/kong/plugins/rate-limiting/daos.lua index dc3127ac7c1..707dc7f7ac0 100644 --- a/kong/plugins/rate-limiting/daos.lua +++ b/kong/plugins/rate-limiting/daos.lua @@ -4,7 +4,7 @@ local timestamp = require "kong.tools.timestamp" local RateLimitingMetrics = BaseDao:extend() -function RateLimitingMetrics:new(properties) +function RateLimitingMetrics:new(properties, events_handler) self._table = "ratelimiting_metrics" self.queries = { increment_counter = [[ UPDATE ratelimiting_metrics SET value = value + ? WHERE api_id = ? AND @@ -21,7 +21,7 @@ function RateLimitingMetrics:new(properties) period = ?; ]] } - RateLimitingMetrics.super.new(self, properties) + RateLimitingMetrics.super.new(self, properties, events_handler) end function RateLimitingMetrics:increment(api_id, identifier, current_timestamp, value) diff --git a/kong/plugins/response-ratelimiting/daos.lua b/kong/plugins/response-ratelimiting/daos.lua index 16fd17f4873..6dbf79d0eb9 100644 --- a/kong/plugins/response-ratelimiting/daos.lua +++ b/kong/plugins/response-ratelimiting/daos.lua @@ -4,7 +4,7 @@ local timestamp = require "kong.tools.timestamp" local ResponseRateLimitingMetrics = BaseDao:extend() -function ResponseRateLimitingMetrics:new(properties) +function ResponseRateLimitingMetrics:new(properties, events_handler) self._table = "response_ratelimiting_metrics" self.queries = { increment_counter = [[ UPDATE response_ratelimiting_metrics SET value = value + ? WHERE api_id = ? AND @@ -21,7 +21,7 @@ function ResponseRateLimitingMetrics:new(properties) period = ?; ]] } - ResponseRateLimitingMetrics.super.new(self, properties) + ResponseRateLimitingMetrics.super.new(self, properties, events_handler) end function ResponseRateLimitingMetrics:increment(api_id, identifier, current_timestamp, value, name) diff --git a/kong/plugins/ssl/hooks.lua b/kong/plugins/ssl/hooks.lua new file mode 100644 index 00000000000..264125a5379 --- /dev/null +++ b/kong/plugins/ssl/hooks.lua @@ -0,0 +1,19 @@ +local events = require "kong.core.events" +local cache = require "kong.tools.database_cache" + +local function invalidate(message_t) + if message_t.collection == "apis" then + cache.delete(cache.ssl_data(message_t.entity.id)) + elseif message_t.collection == "plugins" then + cache.delete(cache.ssl_data(message_t.old_entity and message_t.old_entity.api_id or message_t.entity.api_id)) + end +end + +return { + [events.TYPES.ENTITY_UPDATED] = function(message_t) + invalidate(message_t) + end, + [events.TYPES.ENTITY_DELETED] = function(message_t) + invalidate(message_t) + end +} \ No newline at end of file diff --git a/kong/plugins/ssl/schema.lua b/kong/plugins/ssl/schema.lua index 11c897eacd7..3b54a766b6c 100644 --- a/kong/plugins/ssl/schema.lua +++ b/kong/plugins/ssl/schema.lua @@ -2,19 +2,19 @@ local ssl_util = require "kong.plugins.ssl.ssl_util" local base64 = require "base64" local function validate_cert(v) - local der = ssl_util.cert_to_der(v) + local der, err = ssl_util.cert_to_der(v) if der then return true, nil, { _cert_der_cache = base64.encode(der) } end - return false, "Invalid data" + return false, "Invalid data: "..err end local function validate_key(v) - local der = ssl_util.key_to_der(v) + local der, err = ssl_util.key_to_der(v) if der then return true, nil, { _key_der_cache = base64.encode(der) } end - return false, "Invalid data" + return false, "Invalid data: "..err end return { @@ -25,7 +25,10 @@ return { only_https = { required = false, type = "boolean", default = false }, -- Internal use - _cert_der_cache = { type = "string", immutable = true }, - _key_der_cache = { type = "string", immutable = true } - } + _cert_der_cache = { type = "string" }, + _key_der_cache = { type = "string" } + }, + marshall_event = function(self, t) + return {} -- We don't need any value in the cache event + end } diff --git a/kong/plugins/ssl/ssl_util.lua b/kong/plugins/ssl/ssl_util.lua index d5fcbcf2202..25c649a1ef7 100644 --- a/kong/plugins/ssl/ssl_util.lua +++ b/kong/plugins/ssl/ssl_util.lua @@ -12,16 +12,18 @@ local function execute_openssl(data, cmd) IO.write_to_file(input, data) -- Execute OpenSSL command - local _, code = IO.os_execute(string.format(cmd, input, output)) + local res, code = IO.os_execute(string.format(cmd, input, output)) if code == 0 then result = IO.read_file(output) - end - -- Remove temp files - os.remove(input) - os.remove(output) + -- Remove temp files + os.remove(input) + os.remove(output) - return result + return result + else + return false, res + end end function _M.cert_to_der(data) diff --git a/kong/tools/config_defaults.lua b/kong/tools/config_defaults.lua index 970db06e32c..c4f0e81425e 100644 --- a/kong/tools/config_defaults.lua +++ b/kong/tools/config_defaults.lua @@ -27,6 +27,15 @@ return { } } }, + ["cluster"] = { + type = "table", + content = { + ["bind"] = {type = "string", default = "0.0.0.0:7946"}, + ["iface"] = {type = "string", nullable = true}, + ["rpc-addr"] = {type = "string", default = "127.0.0.1:7373"}, + ["encrypt"] = {type = "string", nullable = true} + } + }, ["database"] = {type = "string", default = "cassandra"}, ["databases_available"] = { type = "table", @@ -50,10 +59,9 @@ return { } } }, - ["database_cache_expiration"] = {type = "number", default = 5}, ["ssl_cert_path"] = {type = "string", nullable = true}, ["ssl_key_path"] = {type = "string", nullable = true}, - ["send_anonymous_reports"] = {type = "boolean", default = false}, + ["send_anonymous_reports"] = {type = "boolean", default = true}, ["memory_cache_size"] = {type = "number", default = 128, min = 32}, ["nginx"] = {type = "string", nullable = true} } diff --git a/kong/tools/config_loader.lua b/kong/tools/config_loader.lua index c48c73e7c42..8ec9e79aa94 100644 --- a/kong/tools/config_loader.lua +++ b/kong/tools/config_loader.lua @@ -1,7 +1,7 @@ local yaml = require "yaml" local IO = require "kong.tools.io" local utils = require "kong.tools.utils" -local cutils = require "kong.cli.utils" +local logger = require "kong.cli.utils.logger" local stringy = require "stringy" local constants = require "kong.constants" local config_defaults = require "kong.tools.config_defaults" @@ -92,7 +92,8 @@ end function _M.load(config_path) local config_contents = IO.read_file(config_path) if not config_contents then - cutils.logger:error_exit("No configuration file at: "..config_path) + logger:error("No configuration file at: "..config_path) + os.exit(1) end local config = yaml.load(config_contents) @@ -103,9 +104,10 @@ function _M.load(config_path) if type(config_error) == "table" then config_error = table.concat(config_error, ", ") end - cutils.logger:warn(string.format("%s: %s", config_key, config_error)) + logger:warn(string.format("%s: %s", config_key, config_error)) end - cutils.logger:error_exit("Invalid properties in given configuration file") + logger:error("Invalid properties in given configuration file") + os.exit(1) end -- Adding computed properties diff --git a/kong/tools/dao_loader.lua b/kong/tools/dao_loader.lua index 30cb1897954..66f6a17dffd 100644 --- a/kong/tools/dao_loader.lua +++ b/kong/tools/dao_loader.lua @@ -1,8 +1,8 @@ local _M = {} -function _M.load(config) - local DaoFactory = require("kong.dao."..config.database..".factory") - return DaoFactory(config.dao_config, config.plugins_available) +function _M.load(parsed_config, events_handler) + local DaoFactory = require("kong.dao."..parsed_config.database..".factory") + return DaoFactory(parsed_config.dao_config, parsed_config.plugins_available, events_handler) end return _M diff --git a/kong/tools/database_cache.lua b/kong/tools/database_cache.lua index 18dcd86a288..9f1338fdaf3 100644 --- a/kong/tools/database_cache.lua +++ b/kong/tools/database_cache.lua @@ -5,33 +5,32 @@ local CACHE_KEYS = { CONSUMERS = "consumers", PLUGINS = "plugins", BASICAUTH_CREDENTIAL = "basicauth_credentials", + HMACAUTH_CREDENTIAL = "hmacauth_credentials", KEYAUTH_CREDENTIAL = "keyauth_credentials", OAUTH2_CREDENTIAL = "oauth2_credentials", + JWTAUTH_CREDENTIAL = "jwtauth_credentials", OAUTH2_TOKEN = "oauth2_token", ACLS = "acls", SSL = "ssl", REQUESTS = "requests", - TIMERS = "timers" + TIMERS = "timers", + ALL_APIS_BY_DIC = "ALL_APIS_BY_DIC" } local _M = {} -function _M.rawset(key, value, exptime) +function _M.rawset(key, value) local cache = ngx.shared.cache - return cache:set(key, value, exptime or 0) + return cache:set(key, value) end -function _M.set(key, value, exptime) - if exptime == nil then - exptime = configuration and configuration.database_cache_expiration or 0 - end - +function _M.set(key, value) if value then value = cjson.encode(value) ngx.log(ngx.DEBUG, " saving cache key \""..key.."\": "..value) end - return _M.rawset(key, value, exptime) + return _M.rawset(key, value) end function _M.rawget(key) @@ -60,47 +59,64 @@ function _M.delete(key) cache:delete(key) end +function _M.delete_all() + local cache = ngx.shared.cache + cache:flush_all() +end + function _M.requests_key() return CACHE_KEYS.REQUESTS end function _M.api_key(host) - return CACHE_KEYS.APIS.."/"..host + return CACHE_KEYS.APIS..":"..host end function _M.consumer_key(id) - return CACHE_KEYS.CONSUMERS.."/"..id + return CACHE_KEYS.CONSUMERS..":"..id end function _M.plugin_key(name, api_id, consumer_id) - return CACHE_KEYS.PLUGINS.."/"..name.."/"..api_id..(consumer_id and "/"..consumer_id or "") + return CACHE_KEYS.PLUGINS..":"..name..":"..api_id..(consumer_id and ":"..consumer_id or "") end function _M.basicauth_credential_key(username) - return CACHE_KEYS.BASICAUTH_CREDENTIAL.."/"..username + return CACHE_KEYS.BASICAUTH_CREDENTIAL..":"..username end function _M.oauth2_credential_key(client_id) - return CACHE_KEYS.OAUTH2_CREDENTIAL.."/"..client_id + return CACHE_KEYS.OAUTH2_CREDENTIAL..":"..client_id end function _M.oauth2_token_key(access_token) - return CACHE_KEYS.OAUTH2_TOKEN.."/"..access_token + return CACHE_KEYS.OAUTH2_TOKEN..":"..access_token end function _M.keyauth_credential_key(key) - return CACHE_KEYS.KEYAUTH_CREDENTIAL.."/"..key + return CACHE_KEYS.KEYAUTH_CREDENTIAL..":"..key +end + +function _M.hmacauth_credential_key(username) + return CACHE_KEYS.HMACAUTH_CREDENTIAL..":"..username +end + +function _M.jwtauth_credential_key(secret) + return CACHE_KEYS.JWTAUTH_CREDENTIAL..":"..secret end function _M.acls_key(consumer_id) - return CACHE_KEYS.ACLS.."/"..consumer_id + return CACHE_KEYS.ACLS..":"..consumer_id end function _M.ssl_data(api_id) - return CACHE_KEYS.SSL.."/"..api_id + return CACHE_KEYS.SSL..":"..api_id +end + +function _M.all_apis_by_dict_key() + return CACHE_KEYS.ALL_APIS_BY_DIC end -function _M.get_or_set(key, cb, exptime) +function _M.get_or_set(key, cb) local value, err -- Try to get value = _M.get(key) @@ -110,7 +126,7 @@ function _M.get_or_set(key, cb, exptime) if err then return nil, err elseif value then - local ok, err = _M.set(key, value, exptime) + local ok, err = _M.set(key, value) if not ok then ngx.log(ngx.ERR, err) end diff --git a/kong/tools/io.lua b/kong/tools/io.lua index 2c8c521d6d7..7b10d3afb63 100644 --- a/kong/tools/io.lua +++ b/kong/tools/io.lua @@ -26,7 +26,7 @@ end -- @param command OS command to execute -- @return string containing command output (both stdout and stderr) -- @return exitcode -function _M.os_execute(command) +function _M.os_execute(command, preserve_output) local n = os.tmpname() -- get a temporary file name to store output local f = os.tmpname() -- get a temporary file name to store script _M.write_to_file(f, command) @@ -34,7 +34,7 @@ function _M.os_execute(command) local result = _M.read_file(n) os.remove(n) os.remove(f) - return string.gsub(string.gsub(result, "^"..f..":[%s%w]+:%s*", ""), "[%\r%\n]", ""), exit_code / 256 + return preserve_output and result or string.gsub(string.gsub(result, "^"..f..":[%s%w]+:%s*", ""), "[%\r%\n]", ""), exit_code / 256 end --- @@ -85,6 +85,7 @@ function _M.write_to_file(path, value) return true end + --- Get the filesize. -- @param path path to file to check -- @return size of file, or `nil` on failure diff --git a/kong/tools/printable.lua b/kong/tools/printable.lua index 6228870f2cb..688fcb341d9 100644 --- a/kong/tools/printable.lua +++ b/kong/tools/printable.lua @@ -14,7 +14,7 @@ function printable_mt:__tostring() v = table.concat(v, ",") end - table.insert(t, k.."="..tostring(v)) + table.insert(t, (type(k) == "string" and k.."=" or "")..tostring(v)) end return table.concat(t, " ") end diff --git a/kong/tools/utils.lua b/kong/tools/utils.lua index 8423d3cd5e8..01b2a58f5e0 100644 --- a/kong/tools/utils.lua +++ b/kong/tools/utils.lua @@ -5,6 +5,18 @@ local uuid = require "lua_uuid" local _M = {} + +--- Retrieves the hostname of the local machine +-- @return string The hostname +function _M.get_hostname() + local f = io.popen ("/bin/hostname") + local hostname = f:read("*a") or "" + f:close() + hostname = string.gsub(hostname, "\n$", "") + return hostname +end + + --- Generates a random unique string -- @return string The random string (a uuid without hyphens) function _M.random_string() diff --git a/spec/integration/admin_api/cache_routes_spec.lua b/spec/integration/admin_api/cache_routes_spec.lua new file mode 100644 index 00000000000..2d6e8b00db9 --- /dev/null +++ b/spec/integration/admin_api/cache_routes_spec.lua @@ -0,0 +1,90 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local cache = require "kong.tools.database_cache" + +local GET_URL = spec_helper.STUB_GET_URL + +describe("Admin API", function() + + setup(function() + spec_helper.prepare_db() + spec_helper.insert_fixtures { + api = { + {name = "api-cache", request_host = "cache.com", upstream_url = "http://mockbin.org/"}, + } + } + + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + describe("/cache/", function() + local BASE_URL = spec_helper.API_URL.."/cache/" + + describe("GET", function() + + it("[FAILURE] should return an error when the key is invalid", function() + local _, status = http_client.get(BASE_URL.."hello") + assert.equal(404, status) + end) + + it("[SUCCESS] should get the value of a cache item", function() + -- Populating cache + local _, status = http_client.get(GET_URL, {}, {host = "cache.com"}) + assert.equal(200, status) + + -- Retrieving cache + local response, status = http_client.get(BASE_URL..cache.all_apis_by_dict_key()) + assert.equal(200, status) + assert.truthy(json.decode(response).by_dns) + end) + + end) + + describe("DELETE", function() + + it("[SUCCESS] should invalidate an entity", function() + -- Populating cache + local _, status = http_client.get(GET_URL, {}, {host = "cache.com"}) + assert.equal(200, status) + + -- Retrieving cache + local response, status = http_client.get(BASE_URL..cache.all_apis_by_dict_key()) + assert.equal(200, status) + assert.truthy(json.decode(response).by_dns) + + -- Delete + local _, status = http_client.delete(BASE_URL..cache.all_apis_by_dict_key()) + assert.equal(200, status) + + -- Make sure it doesn't exist + local _, status = http_client.get(BASE_URL..cache.all_apis_by_dict_key()) + assert.equal(404, status) + end) + + it("[SUCCESS] should invalidate all entities", function() + -- Populating cache + local _, status = http_client.get(GET_URL, {}, {host = "cache.com"}) + assert.equal(200, status) + + -- Retrieving cache + local response, status = http_client.get(BASE_URL..cache.all_apis_by_dict_key()) + assert.equal(200, status) + assert.truthy(json.decode(response).by_dns) + + -- Delete + local _, status = http_client.delete(BASE_URL) + assert.equal(200, status) + + -- Make sure it doesn't exist + local _, status = http_client.get(BASE_URL..cache.all_apis_by_dict_key()) + assert.equal(404, status) + end) + end) + + end) +end) \ No newline at end of file diff --git a/spec/integration/admin_api/cluster_routes_spec.lua b/spec/integration/admin_api/cluster_routes_spec.lua new file mode 100644 index 00000000000..8b843144626 --- /dev/null +++ b/spec/integration/admin_api/cluster_routes_spec.lua @@ -0,0 +1,55 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local utils = require "kong.tools.utils" + +describe("Admin API", function() + + setup(function() + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + describe("/cluster/", function() + local BASE_URL = spec_helper.API_URL.."/cluster/" + + describe("GET", function() + + it("[SUCCESS] should get the list of members", function() + local response, status = http_client.get(BASE_URL, {}, {}) + assert.equal(200, status) + local body = json.decode(response) + assert.truthy(body) + assert.equal(1, #body) + + local member = table.remove(body, 1) + assert.equal(4, utils.table_size(member)) + assert.truthy(member.addr) + assert.truthy(member.status) + assert.truthy(member.name) + assert.truthy(member.port) + + assert.equal("alive", member.status) + end) + + end) + + end) + + describe("/cluster/events/", function() + local BASE_URL = spec_helper.API_URL.."/cluster/events" + + describe("POST", function() + + it("[SUCCESS] should post a new event", function() + local _, status = http_client.post(BASE_URL, {}, {}) + assert.equal(200, status) + end) + + end) + + end) +end) \ No newline at end of file diff --git a/spec/integration/admin_api/kong_routes_spec.lua b/spec/integration/admin_api/kong_routes_spec.lua index f6d1d4f920c..7d4a1af0f15 100644 --- a/spec/integration/admin_api/kong_routes_spec.lua +++ b/spec/integration/admin_api/kong_routes_spec.lua @@ -1,7 +1,6 @@ local json = require "cjson" local http_client = require "kong.tools.http_client" local spec_helper = require "spec.spec_helpers" -local IO = require "kong.tools.io" local utils = require "kong.tools.utils" local env = spec_helper.get_env() -- test environment local dao_factory = env.dao_factory diff --git a/spec/integration/admin_api/route_helpers_spec.lua b/spec/integration/admin_api/route_helpers_spec.lua index c35d23419d9..e817179b086 100644 --- a/spec/integration/admin_api/route_helpers_spec.lua +++ b/spec/integration/admin_api/route_helpers_spec.lua @@ -1,9 +1,10 @@ local route_helpers = require "kong.api.route_helpers" +local utils = require "kong.tools.utils" describe("Route Helpers", function() it("should return the hostname", function() - assert.truthy(route_helpers.get_hostname()) + assert.truthy(utils.get_hostname()) end) it("should return parse the nginx status", function() diff --git a/spec/integration/cli/quit_spec.lua b/spec/integration/cli/cmds/quit_spec.lua similarity index 100% rename from spec/integration/cli/quit_spec.lua rename to spec/integration/cli/cmds/quit_spec.lua diff --git a/spec/integration/cli/reload_spec.lua b/spec/integration/cli/cmds/reload_spec.lua similarity index 100% rename from spec/integration/cli/reload_spec.lua rename to spec/integration/cli/cmds/reload_spec.lua diff --git a/spec/integration/cli/restart_spec.lua b/spec/integration/cli/cmds/restart_spec.lua similarity index 66% rename from spec/integration/cli/restart_spec.lua rename to spec/integration/cli/cmds/restart_spec.lua index f89ff1944d2..69a9e27dee1 100644 --- a/spec/integration/cli/restart_spec.lua +++ b/spec/integration/cli/cmds/restart_spec.lua @@ -1,5 +1,4 @@ local spec_helper = require "spec.spec_helpers" -local IO = require "kong.tools.io" describe("CLI", function() @@ -26,17 +25,9 @@ describe("CLI", function() end) it("should restart kong when it's crashed", function() - local kong_pid = IO.read_file(spec_helper.get_env().configuration.pid_file) os.execute("pkill -9 nginx") - - repeat - -- Wait till it's really over - local _, code = IO.os_execute("kill -0 "..kong_pid) - until(code ~= 0) - - local res, code = spec_helper.restart_kong() + local _, code = spec_helper.restart_kong() assert.are.same(0, code) - assert.truthy(res:match("It seems like Kong crashed the last time it was started")) end) end) diff --git a/spec/integration/cli/start_spec.lua b/spec/integration/cli/cmds/start_spec.lua similarity index 100% rename from spec/integration/cli/start_spec.lua rename to spec/integration/cli/cmds/start_spec.lua diff --git a/spec/integration/cli/version_spec.lua b/spec/integration/cli/cmds/version_spec.lua similarity index 100% rename from spec/integration/cli/version_spec.lua rename to spec/integration/cli/cmds/version_spec.lua diff --git a/spec/integration/cli/services/dnsmasq_spec.lua b/spec/integration/cli/services/dnsmasq_spec.lua new file mode 100644 index 00000000000..bcc503fa7fe --- /dev/null +++ b/spec/integration/cli/services/dnsmasq_spec.lua @@ -0,0 +1,31 @@ +local spec_helper = require "spec.spec_helpers" +local configuration = require("kong.cli.utils.configuration").parse(spec_helper.get_env().conf_file) +local Dnsmasq = require("kong.cli.services.dnsmasq")(configuration.value) + +describe("Dnsmasq", function() + + it("should start and stop", function() + local ok, err = Dnsmasq:start() + assert.truthy(ok) + assert.falsy(err) + + assert.truthy(Dnsmasq:is_running()) + + -- Trying again will fail + local ok, err = Dnsmasq:start() + assert.falsy(ok) + assert.truthy(err) + assert.equal("dnsmasq is already running", err) + + Dnsmasq:stop() + + assert.falsy(Dnsmasq:is_running()) + end) + + it("should stop even when not running", function() + assert.falsy(Dnsmasq:is_running()) + Dnsmasq:stop() + assert.falsy(Dnsmasq:is_running()) + end) + +end) diff --git a/spec/integration/cli/services/nginx_spec.lua b/spec/integration/cli/services/nginx_spec.lua new file mode 100644 index 00000000000..85a83f9db37 --- /dev/null +++ b/spec/integration/cli/services/nginx_spec.lua @@ -0,0 +1,131 @@ +local spec_helper = require "spec.spec_helpers" +local configuration = require("kong.cli.utils.configuration").parse(spec_helper.get_env().conf_file) +local nginx = require("kong.cli.services.nginx")(configuration.value, configuration.path) + +local TIMEOUT = 10 + +describe("Nginx", function() + + after_each(function() + local prepare_res, err = nginx:prepare() + assert.falsy(err) + assert.truthy(prepare_res) + + nginx:stop(prepare_res) + + -- Wait for process to quit, with a timeout + local start = os.time() + while (nginx:is_running() and os.time() < (start + TIMEOUT)) do + -- Wait + end + end) + + it("should prepare", function() + local ok, err = nginx:prepare() + assert.falsy(err) + assert.truthy(ok) + + assert.truthy(nginx._parsed_config) + assert.truthy(type(nginx._parsed_config) == "table") + + assert.truthy(nginx._kong_config_path) + end) + + it("should start and stop", function() + local ok, err = nginx:prepare() + assert.falsy(err) + assert.truthy(ok) + + local ok, err = nginx:start() + print(err) + assert.truthy(ok) + assert.falsy(err) + + assert.truthy(nginx:is_running()) + + -- Trying again will fail + local ok, err = nginx:start() + assert.falsy(ok) + assert.truthy(err) + assert.equal("nginx is already running", err) + + nginx:stop() + assert.falsy(nginx:is_running()) + end) + + it("should stop even when not running", function() + local ok, err = nginx:prepare() + assert.falsy(err) + assert.truthy(ok) + + assert.falsy(nginx:is_running()) + nginx:stop() + assert.falsy(nginx:is_running()) + end) + + it("should quit", function() + local ok, err = nginx:prepare() + assert.falsy(err) + assert.truthy(ok) + + assert.falsy(nginx:is_running()) + + local ok, err = nginx:start() + assert.truthy(ok) + assert.falsy(err) + + assert.truthy(nginx:is_running()) + local ok, err = nginx:quit() + assert.truthy(ok) + assert.falsy(err) + + -- Wait for process to quit, with a timeout + local start = os.time() + while (nginx:is_running() and os.time() < (start + TIMEOUT)) do + -- Wait + end + assert.falsy(nginx:is_running()) + end) + + it("should not quit when not running", function() + local ok, err = nginx:prepare() + assert.falsy(err) + assert.truthy(ok) + + assert.falsy(nginx:is_running()) + local ok, err = nginx:quit() + assert.falsy(ok) + assert.truthy(err) + + -- Wait for process to quit, with a timeout + local start = os.time() + while (nginx:is_running() and os.time() < (start + TIMEOUT)) do + -- Wait + end + assert.falsy(nginx:is_running()) + end) + + it("should reload", function() + local ok, err = nginx:prepare() + assert.falsy(err) + assert.truthy(ok) + + assert.falsy(nginx:is_running()) + + local ok, err = nginx:start() + assert.truthy(ok) + assert.falsy(err) + + local pid = nginx:is_running() + assert.truthy(pid) + + local ok, err = nginx:reload() + assert.truthy(ok) + assert.falsy(err) + + local new_pid = nginx:is_running() + assert.truthy(new_pid) + assert.truthy(pid == new_pid) + end) + +end) diff --git a/spec/integration/cli/services/serf_spec.lua b/spec/integration/cli/services/serf_spec.lua new file mode 100644 index 00000000000..414c1bbed0a --- /dev/null +++ b/spec/integration/cli/services/serf_spec.lua @@ -0,0 +1,31 @@ +local spec_helper = require "spec.spec_helpers" +local configuration = require("kong.cli.utils.configuration").parse(spec_helper.get_env().conf_file) +local Serf = require("kong.cli.services.serf")(configuration.value) + +describe("Serf", function() + + it("should start and stop", function() + local ok, err = Serf:start() + assert.truthy(ok) + assert.falsy(err) + + assert.truthy(Serf:is_running()) + + -- Trying again will fail + local ok, err = Serf:start() + assert.falsy(ok) + assert.truthy(err) + assert.equal("serf is already running", err) + + Serf:stop() + + assert.falsy(Serf:is_running()) + end) + + it("should stop even when not running", function() + assert.falsy(Serf:is_running()) + Serf:stop() + assert.falsy(Serf:is_running()) + end) + +end) diff --git a/spec/integration/cli/utils/luarocks_spec.lua b/spec/integration/cli/utils/luarocks_spec.lua new file mode 100644 index 00000000000..0024cf3731a --- /dev/null +++ b/spec/integration/cli/utils/luarocks_spec.lua @@ -0,0 +1,21 @@ +local luarocks = require "kong.cli.utils.luarocks" + +describe("Luarocks", function() + + it("should get luarocks dir", function() + local res = luarocks.get_dir() + assert.truthy(res.name) + assert.truthy(res.root) + end) + + it("should get luarocks config dir", function() + local res = luarocks.get_config_dir() + assert.truthy(res) + end) + + it("should get luarocks install dir", function() + local res = luarocks.get_install_dir() + assert.truthy(res) + end) + +end) diff --git a/spec/integration/core/hooks_spec.lua b/spec/integration/core/hooks_spec.lua new file mode 100644 index 00000000000..d4fafa9462f --- /dev/null +++ b/spec/integration/core/hooks_spec.lua @@ -0,0 +1,395 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local cache = require "kong.tools.database_cache" +local utils = require "kong.tools.utils" + +local STUB_GET_URL = spec_helper.STUB_GET_URL +local API_URL = spec_helper.API_URL + +describe("Core Hooks", function() + + setup(function() + spec_helper.prepare_db() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + before_each(function() + spec_helper.restart_kong() + + spec_helper.drop_db() + spec_helper.insert_fixtures { + api = { + {request_host = "hooks1.com", upstream_url = "http://mockbin.com"}, + {request_host = "hooks-consumer.com", upstream_url = "http://mockbin.com"}, + {request_host = "hooks-plugins.com", upstream_url = "http://mockbin.com"} + }, + consumer = { + {username = "consumer1"} + }, + plugin = { + {name = "basic-auth", config = {}, __api = 2}, + {name = "basic-auth", config = {}, __api = 3}, + {name = "rate-limiting", config = {minute=10}, __api = 3}, + {name = "rate-limiting", config = {minute=3}, __api = 3, __consumer = 1} + }, + basicauth_credential = { + {username = "user123", password = "pass123", __consumer = 1} + } + } + end) + + describe("Plugin entity invalidation", function() + it("should invalidate a plugin when deleting", function() + -- Making a request to populate the cache + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks-consumer.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/apis", {request_host="hooks-consumer.com"}) + assert.equals(200, status) + local api_id = table.remove(json.decode(response).data).id + assert.truthy(api_id) + + local _, status = http_client.get(API_URL.."/cache/"..cache.plugin_key("basic-auth", api_id, nil)) + assert.equals(200, status) + + -- Delete plugin + local response, status = http_client.get(API_URL.."/apis/"..api_id.."/plugins/", {name="basic-auth"}) + assert.equals(200, status) + local plugin_id = table.remove(json.decode(response).data, 1).id + assert.truthy(plugin_id) + + local _, status = http_client.delete(API_URL.."/apis/"..api_id.."/plugins/"..plugin_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache.plugin_key("basic-auth", api_id, nil)) + if status ~= 200 then + exists = false + end + end + + -- Consuming the API again without any authorization + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks-consumer.com"}) + assert.equals(200, status) + end) + it("should invalidate a consumer-specific plugin when deleting", function() + -- Making a request to populate the cache + local _, status, headers = http_client.get(STUB_GET_URL, {}, {host = "hooks-plugins.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + assert.equals(3, tonumber(headers["x-ratelimit-limit-minute"])) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/apis", {request_host="hooks-plugins.com"}) + assert.equals(200, status) + local api_id = table.remove(json.decode(response).data).id + assert.truthy(api_id) + + local response, status = http_client.get(API_URL.."/consumers/consumer1") + assert.equals(200, status) + local consumer_id = json.decode(response).id + assert.truthy(consumer_id) + + local _, status = http_client.get(API_URL.."/cache/"..cache.plugin_key("rate-limiting", api_id, consumer_id)) + assert.equals(200, status) + + -- Delete plugin + local response, status = http_client.get(API_URL.."/apis/"..api_id.."/plugins/", {name="rate-limiting", consumer_id=consumer_id}) + assert.equals(200, status) + local plugin_id = table.remove(json.decode(response).data, 1).id + assert.truthy(plugin_id) + + local _, status = http_client.delete(API_URL.."/apis/"..api_id.."/plugins/"..plugin_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache.plugin_key("rate-limiting", api_id, consumer_id)) + if status ~= 200 then + exists = false + end + end + + -- Consuming the API again + local _, status, headers = http_client.get(STUB_GET_URL, {}, {host = "hooks-plugins.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + assert.equals(10, tonumber(headers["x-ratelimit-limit-minute"])) + end) + it("should invalidate a consumer-specific plugin when updating", function() + -- Making a request to populate the cache + local _, status, headers = http_client.get(STUB_GET_URL, {}, {host = "hooks-plugins.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + assert.equals(3, tonumber(headers["x-ratelimit-limit-minute"])) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/apis", {request_host="hooks-plugins.com"}) + assert.equals(200, status) + local api_id = table.remove(json.decode(response).data).id + assert.truthy(api_id) + + local response, status = http_client.get(API_URL.."/consumers/consumer1") + assert.equals(200, status) + local consumer_id = json.decode(response).id + assert.truthy(consumer_id) + + local _, status = http_client.get(API_URL.."/cache/"..cache.plugin_key("rate-limiting", api_id, consumer_id)) + assert.equals(200, status) + + -- Delete plugin + local response, status = http_client.get(API_URL.."/apis/"..api_id.."/plugins/", {name="rate-limiting", consumer_id=consumer_id}) + assert.equals(200, status) + local plugin_id = table.remove(json.decode(response).data, 1).id + assert.truthy(plugin_id) + + local _, status = http_client.patch(API_URL.."/apis/"..api_id.."/plugins/"..plugin_id, {enabled=false}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache.plugin_key("rate-limiting", api_id, consumer_id)) + if status ~= 200 then + exists = false + end + end + + -- Consuming the API again + local _, status, headers = http_client.get(STUB_GET_URL, {}, {host = "hooks-plugins.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + assert.equals(10, tonumber(headers["x-ratelimit-limit-minute"])) + end) + it("should invalidate a plugin when updating", function() + -- Making a request to populate the cache + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks-consumer.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/apis", {request_host="hooks-consumer.com"}) + assert.equals(200, status) + local api_id = table.remove(json.decode(response).data).id + assert.truthy(api_id) + + local _, status = http_client.get(API_URL.."/cache/"..cache.plugin_key("basic-auth", api_id, nil)) + assert.equals(200, status) + + -- Delete plugin + local response, status = http_client.get(API_URL.."/apis/"..api_id.."/plugins/", {name="basic-auth"}) + assert.equals(200, status) + local plugin_id = table.remove(json.decode(response).data, 1).id + assert.truthy(plugin_id) + + local _, status = http_client.patch(API_URL.."/apis/"..api_id.."/plugins/"..plugin_id, {enabled=false}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache.plugin_key("basic-auth", api_id, nil)) + if status ~= 200 then + exists = false + end + end + + -- Consuming the API again without any authorization + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks-consumer.com"}) + assert.equals(200, status) + end) + end) + + describe("Consumer entity invalidation", function() + it("should invalidate a consumer when deleting", function() + -- Making a request to populate the cache + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks-consumer.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/consumers/consumer1") + assert.equals(200, status) + local consumer_id = json.decode(response).id + assert.truthy(consumer_id) + + local response, status = http_client.get(API_URL.."/cache/"..cache.consumer_key(consumer_id)) + assert.equals(200, status) + assert.equals("consumer1", json.decode(response).username) + + -- Delete consumer + local _, status = http_client.delete(API_URL.."/consumers/consumer1") + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache.consumer_key(consumer_id)) + if status ~= 200 then + exists = false + end + end + + -- Consuming the API again + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks-consumer.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(403, status) + end) + + it("should invalidate a consumer when updating", function() + -- Making a request to populate the cache + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks-consumer.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/consumers/consumer1") + assert.equals(200, status) + local consumer_id = json.decode(response).id + assert.truthy(consumer_id) + + local response, status = http_client.get(API_URL.."/cache/"..cache.consumer_key(consumer_id)) + assert.equals(200, status) + assert.equals("consumer1", json.decode(response).username) + + -- Update consumer + local _, status = http_client.patch(API_URL.."/consumers/consumer1", {username="updated_consumer1"}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache.consumer_key(consumer_id)) + if status ~= 200 then + exists = false + end + end + + -- Consuming the API again + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks-consumer.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + + -- Making sure the cache is updated + local response, status = http_client.get(API_URL.."/cache/"..cache.consumer_key(consumer_id)) + assert.equals(200, status) + assert.equals("updated_consumer1", json.decode(response).username) + end) + end) + + describe("API entity invalidation", function() + it("should invalidate ALL_APIS_BY_DICT when adding a new API", function() + -- Making a request to populate ALL_APIS_BY_DICT + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks1.com"}) + assert.equals(200, status) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/cache/"..cache.all_apis_by_dict_key()) + assert.equals(200, status) + assert.truthy(json.decode(response).by_dns["hooks1.com"]) + assert.falsy(json.decode(response).by_dns["dynamic-hooks.com"]) + + -- Adding a new API + local _, status = http_client.post(API_URL.."/apis", {request_host="dynamic-hooks.com", upstream_url="http://mockbin.org"}) + assert.equals(201, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache.all_apis_by_dict_key()) + if status ~= 200 then + exists = false + end + end + + -- Consuming the API again + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks1.com"}) + assert.equals(200, status) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/cache/"..cache.all_apis_by_dict_key()) + assert.equals(200, status) + assert.truthy(json.decode(response).by_dns["hooks1.com"]) + assert.truthy(json.decode(response).by_dns["dynamic-hooks.com"]) + end) + + it("should invalidate ALL_APIS_BY_DICT when updating an API", function() + -- Making a request to populate ALL_APIS_BY_DICT + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks1.com"}) + assert.equals(200, status) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/cache/"..cache.all_apis_by_dict_key()) + assert.equals(200, status) + assert.truthy(json.decode(response).by_dns["hooks1.com"]) + assert.equals("http://mockbin.com", json.decode(response).by_dns["hooks1.com"].upstream_url) + + -- Updating API + local response, status = http_client.get(API_URL.."/apis", {request_host="hooks1.com"}) + assert.equals(200, status) + local api_id = table.remove(json.decode(response).data).id + assert.truthy(api_id) + + local _, status = http_client.patch(API_URL.."/apis/"..api_id, {upstream_url="http://mockbin.org"}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache.all_apis_by_dict_key()) + if status ~= 200 then + exists = false + end + end + + -- Consuming the API again + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks1.com"}) + assert.equals(200, status) + + -- Make sure the cache is populated with updated value + local response, status = http_client.get(API_URL.."/cache/"..cache.all_apis_by_dict_key()) + assert.equals(200, status) + assert.truthy(json.decode(response).by_dns["hooks1.com"]) + assert.equals("http://mockbin.org", json.decode(response).by_dns["hooks1.com"].upstream_url) + end) + + it("should invalidate ALL_APIS_BY_DICT when deleting an API", function() + -- Making a request to populate ALL_APIS_BY_DICT + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks1.com"}) + assert.equals(200, status) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/cache/"..cache.all_apis_by_dict_key()) + assert.equals(200, status) + assert.truthy(1, utils.table_size(json.decode(response).by_dns)) + assert.truthy(json.decode(response).by_dns["hooks1.com"]) + + -- Deleting API + local response, status = http_client.get(API_URL.."/apis", {request_host="hooks1.com"}) + assert.equals(200, status) + local api_id = table.remove(json.decode(response).data).id + assert.truthy(api_id) + + local _, status = http_client.delete(API_URL.."/apis/"..api_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache.all_apis_by_dict_key()) + if status ~= 200 then + exists = false + end + end + + -- Consuming the API again + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hooks1.com"}) + assert.equals(404, status) + + -- Make sure the cache is populated + local response, status = http_client.get(API_URL.."/cache/"..cache.all_apis_by_dict_key()) + assert.equals(200, status) + assert.truthy(0, utils.table_size(json.decode(response).by_dns)) + end) + end) + +end) diff --git a/spec/integration/dao/cassandra/cascade_spec.lua b/spec/integration/dao/cassandra/cascade_spec.lua index afbd506f37b..7e37355a4ef 100644 --- a/spec/integration/dao/cassandra/cascade_spec.lua +++ b/spec/integration/dao/cassandra/cascade_spec.lua @@ -132,9 +132,9 @@ describe("Cassandra cascade delete", function() end) it("should delete foreign keyauth_credentials when deleting a Consumer", function() - local ok, err = dao_factory.consumers:delete(consumer) + local res, err = dao_factory.consumers:delete(consumer) assert.falsy(err) - assert.True(ok) + assert.truthy(res) local results, err = dao_factory.keyauth_credentials:find_by_keys { consumer_id = consumer.id diff --git a/spec/integration/dao/cassandra/events_spec.lua b/spec/integration/dao/cassandra/events_spec.lua new file mode 100644 index 00000000000..0382fd1565e --- /dev/null +++ b/spec/integration/dao/cassandra/events_spec.lua @@ -0,0 +1,123 @@ +local event_types = require "kong.core.events".TYPES +local spec_helper = require "spec.spec_helpers" +local utils = require "kong.tools.utils" + +local env = spec_helper.get_env() -- test environment +local dao_factory = env.dao_factory +local events = env.events + +describe("Events", function() + + setup(function() + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + before_each(function() + spec_helper.prepare_db() + end) + + it("should fire event on insert", function() + local received = false + + events:subscribe(event_types.CLUSTER_PROPAGATE, function(message_t) + if message_t.type == event_types.ENTITY_CREATED then + assert.equals(event_types.ENTITY_CREATED, message_t.type) + assert.equals("apis", message_t.collection) + assert.truthy(message_t.entity) + assert.equals(5, utils.table_size(message_t.entity)) + assert.equals("test.com", message_t.entity.request_host) + assert.equals("http://mockbin.org", message_t.entity.upstream_url) + + received = true + end + end) + + local res, err = dao_factory.apis:insert({ + request_host = "test.com", + upstream_url = "http://mockbin.org" + }) + + assert.truthy(res) + assert.falsy(err) + + while not received do + -- Wait + end + assert.True(received) + end) + + it("should fire event on update", function() + local received = false + + events:subscribe(event_types.CLUSTER_PROPAGATE, function(message_t) + + if message_t.type == event_types.ENTITY_UPDATED then + assert.equals(event_types.ENTITY_UPDATED, message_t.type) + assert.equals("apis", message_t.collection) + assert.truthy(message_t.entity) + assert.equals(5, utils.table_size(message_t.entity)) + assert.equals("test.com", message_t.entity.request_host) + assert.equals("http://mockbin.org", message_t.entity.upstream_url) + + local new_entity = dao_factory.apis:find_by_primary_key({id=message_t.entity.id}) + assert.equals("http://mockbin2.org", new_entity.upstream_url) + + received = true + end + end) + + local res, err = dao_factory.apis:insert({ + request_host = "test.com", + upstream_url = "http://mockbin.org" + }) + assert.truthy(res) + assert.falsy(err) + + -- Update entity + res.upstream_url = "http://mockbin2.org" + local res, err = dao_factory.apis:update(res) + assert.truthy(res) + assert.falsy(err) + + while not received do + -- Wait + end + assert.True(received) + end) + + it("should fire event on delete", function() + local received = false + + events:subscribe(event_types.CLUSTER_PROPAGATE, function(message_t) + if message_t.type == event_types.ENTITY_DELETED then + assert.equals(event_types.ENTITY_DELETED, message_t.type) + assert.equals("apis", message_t.collection) + assert.truthy(message_t.entity) + assert.equals(5, utils.table_size(message_t.entity)) + assert.equals("test.com", message_t.entity.request_host) + assert.equals("http://mockbin.org", message_t.entity.upstream_url) + + received = true + end + end) + + local res, err = dao_factory.apis:insert({ + request_host = "test.com", + upstream_url = "http://mockbin.org" + }) + assert.truthy(res) + assert.falsy(err) + + dao_factory.apis:delete({id=res.id}) + + while not received do + -- Wait + end + assert.True(received) + end) + +end) \ No newline at end of file diff --git a/spec/integration/proxy/database_cache_spec.lua b/spec/integration/proxy/database_cache_spec.lua deleted file mode 100644 index 55cf9d81e71..00000000000 --- a/spec/integration/proxy/database_cache_spec.lua +++ /dev/null @@ -1,67 +0,0 @@ -local spec_helper = require "spec.spec_helpers" -local http_client = require "kong.tools.http_client" - -local env = spec_helper.get_env() - -describe("Database cache", function() - local fixtures - - setup(function() - spec_helper.prepare_db() - fixtures = spec_helper.insert_fixtures { - api = { - {name = "tests-database-cache", request_host = "cache.test", upstream_url = "http://httpbin.org"} - } - } - - spec_helper.start_kong() - end) - - teardown(function() - spec_helper.stop_kong() - end) - - it("should expire cache after five seconds", function() - -- trigger a db fetch for this API's plugins - http_client.get(spec_helper.PROXY_URL.."/get", {}, {host = "cache.test"}) - - -- Let's add the authentication plugin configuration - local _, err = env.dao_factory.plugins:insert { - name = "key-auth", - api_id = fixtures.api[1].id, - config = { - key_names = {"x-key"} - } - } - assert.falsy(err) - - -- Making the request immediately after will succeed - local _, status = http_client.get(spec_helper.PROXY_URL.."/get", {}, {host = "cache.test"}) - assert.are.equal(200, status) - - -- But waiting after the cache expiration (5 seconds) should block the request - os.execute("sleep "..tonumber(5)) - - local _, status = http_client.get(spec_helper.PROXY_URL.."/get", {}, {host = "cache.test"}) - assert.are.equal(401, status) - - -- Create a consumer and a key will make it work again - local consumer, err = env.dao_factory.consumers:insert {username = "john"} - assert.falsy(err) - - local _, err = env.dao_factory.keyauth_credentials:insert { - consumer_id = consumer.id, - key = "secret_key_123" - } - assert.falsy(err) - - -- This should fail, wrong key - local _, status = http_client.get(spec_helper.PROXY_URL.."/get", {}, {host = "cache.test", ["x-key"] = "secret_key"}) - assert.are.equal(403, status) - - -- This should work, right key - local _, status = http_client.get(spec_helper.PROXY_URL.."/get", {}, {host = "cache.test", ["x-key"] = "secret_key_123"}) - assert.are.equal(200, status) - end) - -end) diff --git a/spec/plugins/acl/api_spec.lua b/spec/plugins/acl/api_spec.lua index 02d31b3b6ff..8b28c4e294e 100644 --- a/spec/plugins/acl/api_spec.lua +++ b/spec/plugins/acl/api_spec.lua @@ -116,4 +116,4 @@ describe("ACLs API", function() end) -end) +end) \ No newline at end of file diff --git a/spec/plugins/acl/hooks_spec.lua b/spec/plugins/acl/hooks_spec.lua new file mode 100644 index 00000000000..640b3197d17 --- /dev/null +++ b/spec/plugins/acl/hooks_spec.lua @@ -0,0 +1,163 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local cache = require "kong.tools.database_cache" + +local STUB_GET_URL = spec_helper.STUB_GET_URL +local API_URL = spec_helper.API_URL + +describe("ACL Hooks", function() + + setup(function() + spec_helper.prepare_db() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + before_each(function() + spec_helper.restart_kong() + + spec_helper.drop_db() + spec_helper.insert_fixtures { + api = { + {request_host = "acl1.com", upstream_url = "http://mockbin.com"}, + {request_host = "acl2.com", upstream_url = "http://mockbin.com"} + }, + consumer = { + {username = "consumer1"}, + {username = "consumer2"} + }, + plugin = { + {name = "key-auth", config = {key_names = {"apikey"}}, __api = 1}, + {name = "acl", config = { whitelist = {"admin"}}, __api = 1}, + {name = "key-auth", config = {key_names = {"apikey"}}, __api = 2}, + {name = "acl", config = { whitelist = {"ya"}}, __api = 2} + }, + keyauth_credential = { + {key = "apikey123", __consumer = 1}, + {key = "apikey124", __consumer = 2} + }, + acl = { + {group="admin", __consumer = 1}, + {group="pro", __consumer = 1}, + {group="admin", __consumer = 2} + } + } + + end) + + local function get_consumer_id(username) + local response, status = http_client.get(API_URL.."/consumers/consumer1") + assert.equals(200, status) + local consumer_id = json.decode(response).id + assert.truthy(consumer_id) + return consumer_id + end + + local function get_acl_id(consumer_id_or_name, group_name) + local response, status = http_client.get(API_URL.."/consumers/"..consumer_id_or_name.."/acls/", {group=group_name}) + assert.equals(200, status) + local body = json.decode(response) + if #body.data == 1 then + return table.remove(body.data, 1).id + end + end + + describe("ACL entity invalidation", function() + it("should invalidate when ACL entity is deleted", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host="acl1.com"}) + assert.equals(200, status) + + -- Check that cache is populated + local cache_key = cache.acls_key(get_consumer_id("consumer1")) + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Delete ACL group (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1/acls/"..get_acl_id("consumer1", "admin")) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host="acl1.com"}) + assert.equals(403, status) + end) + it("should invalidate when ACL entity is updated", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host="acl1.com"}) + assert.equals(200, status) + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host="acl2.com"}) + assert.equals(403, status) + + -- Check that cache is populated + local cache_key = cache.acls_key(get_consumer_id("consumer1")) + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Update ACL group (which triggers invalidation) + local _, status = http_client.patch(API_URL.."/consumers/consumer1/acls/"..get_acl_id("consumer1", "admin"), {group="ya"}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host="acl1.com"}) + assert.equals(403, status) + + -- It should work now + local _, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host="acl2.com"}) + assert.equals(200, status) + end) + end) + + describe("Consumer entity invalidation", function() + it("should invalidate when Consumer entity is deleted", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host="acl1.com"}) + assert.equals(200, status) + + -- Check that cache is populated + local cache_key = cache.acls_key(get_consumer_id("consumer1")) + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Delete Consumer (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1") + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host="acl1.com"}) + assert.equals(403, status) + end) + end) + +end) diff --git a/spec/plugins/basic-auth/crypto_spec.lua b/spec/plugins/basic-auth/crypto_spec.lua new file mode 100644 index 00000000000..5359d4c6198 --- /dev/null +++ b/spec/plugins/basic-auth/crypto_spec.lua @@ -0,0 +1,15 @@ +local crypto = require "kong.plugins.basic-auth.crypto" + +describe("Basic Authentication Crypt", function() + it("should encrypt", function() + local credential = { + consumer_id = "id123", + password = "pass123" + } + + local value = crypto.encrypt(credential) + assert.truthy(value) + assert.equals(40, string.len(value)) + assert.equals(crypto.encrypt(credential), crypto.encrypt(credential)) + end) +end) \ No newline at end of file diff --git a/spec/plugins/basic-auth/hooks_spec.lua b/spec/plugins/basic-auth/hooks_spec.lua new file mode 100644 index 00000000000..7cce20e42cc --- /dev/null +++ b/spec/plugins/basic-auth/hooks_spec.lua @@ -0,0 +1,144 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local cache = require "kong.tools.database_cache" + +local STUB_GET_URL = spec_helper.STUB_GET_URL +local API_URL = spec_helper.API_URL + +describe("Basic Authentication Hooks", function() + + setup(function() + spec_helper.prepare_db() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + before_each(function() + spec_helper.restart_kong() + + spec_helper.drop_db() + spec_helper.insert_fixtures { + api = { + {request_host = "basicauth.com", upstream_url = "http://mockbin.com"} + }, + consumer = { + {username = "consumer1"} + }, + plugin = { + {name = "basic-auth", config = {}, __api = 1} + }, + basicauth_credential = { + {username = "user123", password = "pass123", __consumer = 1} + } + } + end) + + describe("Basic Auth Credentials entity invalidation", function() + it("should invalidate when Basic Auth Credential entity is deleted", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, {}, {host="basicauth.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + + -- Check that cache is populated + local cache_key = cache.basicauth_credential_key("user123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/consumer1/basic-auth/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete Basic Auth credential (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1/basic-auth/"..credential_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {}, {host="basicauth.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(403, status) + end) + it("should invalidate when Basic Auth Credential entity is updated", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, {}, {host="basicauth.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {}, {host="basicauth.com", authorization = "Basic aGVsbG8xMjM6cGFzczEyMw=="}) + assert.equals(403, status) + + -- Check that cache is populated + local cache_key = cache.basicauth_credential_key("user123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/consumer1/basic-auth/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete Basic Auth credential (which triggers invalidation) + local _, status = http_client.patch(API_URL.."/consumers/consumer1/basic-auth/"..credential_id, {username="hello123"}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should work + local _, status = http_client.get(STUB_GET_URL, {}, {host="basicauth.com", authorization = "Basic aGVsbG8xMjM6cGFzczEyMw=="}) + assert.equals(200, status) + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {}, {host="basicauth.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(403, status) + end) + end) + describe("Consumer entity invalidation", function() + it("should invalidate when Consumer entity is deleted", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, {}, {host="basicauth.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(200, status) + + -- Check that cache is populated + local cache_key = cache.basicauth_credential_key("user123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Delete Consumer (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1") + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {}, {host="basicauth.com", authorization = "Basic dXNlcjEyMzpwYXNzMTIz"}) + assert.equals(403, status) + end) + end) +end) diff --git a/spec/plugins/hmac-auth/hooks_spec.lua b/spec/plugins/hmac-auth/hooks_spec.lua new file mode 100644 index 00000000000..b8ce4c3423b --- /dev/null +++ b/spec/plugins/hmac-auth/hooks_spec.lua @@ -0,0 +1,164 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local cache = require "kong.tools.database_cache" +local base64 = require "base64" +local crypto = require "crypto" + +local STUB_GET_URL = spec_helper.STUB_GET_URL +local API_URL = spec_helper.API_URL + +describe("HMAC Authentication Hooks", function() + + setup(function() + spec_helper.prepare_db() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + before_each(function() + spec_helper.restart_kong() + + spec_helper.drop_db() + spec_helper.insert_fixtures { + api = { + {request_host = "hmacauth.com", upstream_url = "http://mockbin.com"} + }, + consumer = { + {username = "consumer1"} + }, + plugin = { + {name = "hmac-auth", config = {clock_skew = 3000}, __api = 1} + }, + hmacauth_credential = { + {username = "bob", secret = "secret", __consumer = 1} + } + } + end) + + local function hmac_sha1_binary(secret, data) + return crypto.hmac.digest("sha1", data, secret, true) + end + + local function get_authorization(username) + local date = os.date("!%a, %d %b %Y %H:%M:%S GMT") + local encodedSignature = base64.encode(hmac_sha1_binary("secret", "date: "..date)) + return [["hmac username="]]..username..[[",algorithm="hmac-sha1",headers="date",signature="]]..encodedSignature..[["]], date + end + + describe("HMAC Auth Credentials entity invalidation", function() + it("should invalidate when Hmac Auth Credential entity is deleted", function() + -- It should work + local authorization, date = get_authorization("bob") + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hmacauth.com", date = date, authorization = authorization}) + assert.equals(200, status) + + -- Check that cache is populated + local cache_key = cache.hmacauth_credential_key("bob") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/consumer1/hmac-auth/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete Hmac Auth credential (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1/hmac-auth/"..credential_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local authorization, date = get_authorization("bob") + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hmacauth.com", date = date, authorization = authorization}) + assert.equals(403, status) + end) + it("should invalidate when Hmac Auth Credential entity is updated", function() + -- It should work + local authorization, date = get_authorization("bob") + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hmacauth.com", date = date, authorization = authorization}) + assert.equals(200, status) + + -- It should not work + local authorization, date = get_authorization("hello123") + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hmacauth.com", date = date, authorization = authorization}) + assert.equals(403, status) + + -- Check that cache is populated + local cache_key = cache.hmacauth_credential_key("bob") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/consumer1/hmac-auth/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete Hmac Auth credential (which triggers invalidation) + local _, status = http_client.patch(API_URL.."/consumers/consumer1/hmac-auth/"..credential_id, {username="hello123"}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should work + local authorization, date = get_authorization("hello123") + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hmacauth.com", date = date, authorization = authorization}) + assert.equals(200, status) + + -- It should not work + local authorization, date = get_authorization("bob") + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hmacauth.com", date = date, authorization = authorization}) + assert.equals(403, status) + end) + end) + describe("Consumer entity invalidation", function() + it("should invalidate when Consumer entity is deleted", function() + -- It should work + local authorization, date = get_authorization("bob") + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hmacauth.com", date = date, authorization = authorization}) + assert.equals(200, status) + + -- Check that cache is populated + local cache_key = cache.hmacauth_credential_key("bob") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Delete Consumer (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1") + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local authorization, date = get_authorization("bob") + local _, status = http_client.get(STUB_GET_URL, {}, {host = "hmacauth.com", date = date, authorization = authorization}) + assert.equals(403, status) + end) + end) +end) diff --git a/spec/plugins/jwt/hooks_spec.lua b/spec/plugins/jwt/hooks_spec.lua new file mode 100644 index 00000000000..3c06a656e24 --- /dev/null +++ b/spec/plugins/jwt/hooks_spec.lua @@ -0,0 +1,158 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local cache = require "kong.tools.database_cache" +local jwt_encoder = require "kong.plugins.jwt.jwt_parser" + +local STUB_GET_URL = spec_helper.STUB_GET_URL +local API_URL = spec_helper.API_URL + +describe("JWT Authentication Hooks", function() + + setup(function() + spec_helper.prepare_db() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + before_each(function() + spec_helper.restart_kong() + + spec_helper.drop_db() + spec_helper.insert_fixtures { + api = { + {request_host = "jwt.com", upstream_url = "http://mockbin.com"} + }, + consumer = { + {username = "consumer1"} + }, + plugin = { + {name = "jwt", config = {}, __api = 1} + }, + jwt_secret = { + {key = "key123", secret = "secret123", __consumer = 1} + } + } + end) + + local PAYLOAD = { + iss = nil, + nbf = os.time(), + iat = os.time(), + exp = os.time() + 3600 + } + + local function get_authorization(key, secret) + PAYLOAD.iss = key + local jwt = jwt_encoder.encode(PAYLOAD, secret) + return "Bearer "..jwt + end + + describe("JWT Credentials entity invalidation", function() + it("should invalidate when JWT Auth Credential entity is deleted", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, nil, {host = "jwt.com", authorization = get_authorization("key123", "secret123")}) + assert.equal(200, status) + + -- Check that cache is populated + local cache_key = cache.jwtauth_credential_key("key123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/consumer1/jwt/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete JWT credential (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1/jwt/"..credential_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, nil, {host = "jwt.com", authorization = get_authorization("key123", "secret123")}) + assert.equal(403, status) + end) + it("should invalidate when JWT Auth Credential entity is updated", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, nil, {host = "jwt.com", authorization = get_authorization("key123", "secret123")}) + assert.equal(200, status) + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, nil, {host = "jwt.com", authorization = get_authorization("keyhello", "secret123")}) + assert.equal(403, status) + + -- Check that cache is populated + local cache_key = cache.jwtauth_credential_key("key123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/consumer1/jwt/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete JWT credential (which triggers invalidation) + local _, status = http_client.patch(API_URL.."/consumers/consumer1/jwt/"..credential_id, {key="keyhello"}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should work + local _, status = http_client.get(STUB_GET_URL, nil, {host = "jwt.com", authorization = get_authorization("keyhello", "secret123")}) + assert.equal(200, status) + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, nil, {host = "jwt.com", authorization = get_authorization("key123", "secret123")}) + assert.equal(403, status) + end) + end) + describe("Consumer entity invalidation", function() + it("should invalidate when Consumer entity is deleted", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, nil, {host = "jwt.com", authorization = get_authorization("key123", "secret123")}) + assert.equal(200, status) + + -- Check that cache is populated + local cache_key = cache.jwtauth_credential_key("key123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Delete Consumer (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1") + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, nil, {host = "jwt.com", authorization = get_authorization("key123", "secret123")}) + assert.equal(403, status) + end) + end) +end) diff --git a/spec/plugins/key-auth/hooks_spec.lua b/spec/plugins/key-auth/hooks_spec.lua new file mode 100644 index 00000000000..ed94fdd89ae --- /dev/null +++ b/spec/plugins/key-auth/hooks_spec.lua @@ -0,0 +1,145 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local cache = require "kong.tools.database_cache" + +local STUB_GET_URL = spec_helper.STUB_GET_URL +local API_URL = spec_helper.API_URL + +describe("Key Authentication Hooks", function() + + setup(function() + spec_helper.prepare_db() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + before_each(function() + spec_helper.restart_kong() + + spec_helper.drop_db() + spec_helper.insert_fixtures { + api = { + {request_host = "keyauth.com", upstream_url = "http://mockbin.com"} + }, + consumer = { + {username = "consumer1"} + }, + plugin = { + {name = "key-auth", config = {}, __api = 1} + }, + keyauth_credential = { + {key = "key123", __consumer = 1} + } + } + end) + + describe("Key Auth Credentials entity invalidation", function() + it("should invalidate when Key Auth Credential entity is deleted", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, {apikey="key123"}, {host="keyauth.com"}) + assert.equals(200, status) + + -- Check that cache is populated + local cache_key = cache.keyauth_credential_key("key123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/consumer1/key-auth/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete Key Auth credential (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1/key-auth/"..credential_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {apikey="key123"}, {host="keyauth.com"}) + assert.equals(403, status) + end) + it("should invalidate when Key Auth Credential entity is updated", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, {apikey="key123"}, {host="keyauth.com"}) + assert.equals(200, status) + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {apikey="updkey123"}, {host="keyauth.com"}) + assert.equals(403, status) + + -- Check that cache is populated + local cache_key = cache.keyauth_credential_key("key123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/consumer1/key-auth/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete Key Auth credential (which triggers invalidation) + local _, status = http_client.patch(API_URL.."/consumers/consumer1/key-auth/"..credential_id, {key="updkey123"}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should work + local _, status = http_client.get(STUB_GET_URL, {apikey="updkey123"}, {host="keyauth.com"}) + assert.equals(200, status) + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {apikey="key123"}, {host="keyauth.com"}) + assert.equals(403, status) + end) + end) + + describe("Consumer entity invalidation", function() + it("should invalidate when Consumer entity is deleted", function() + -- It should work + local _, status = http_client.get(STUB_GET_URL, {apikey="key123"}, {host="keyauth.com"}) + assert.equals(200, status) + + -- Check that cache is populated + local cache_key = cache.keyauth_credential_key("key123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Delete Consumer (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/consumer1") + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.get(STUB_GET_URL, {apikey="key123"}, {host="keyauth.com"}) + assert.equals(403, status) + end) + end) +end) diff --git a/spec/plugins/oauth2/hooks_spec.lua b/spec/plugins/oauth2/hooks_spec.lua new file mode 100644 index 00000000000..a53c61d7f2b --- /dev/null +++ b/spec/plugins/oauth2/hooks_spec.lua @@ -0,0 +1,302 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local cache = require "kong.tools.database_cache" +local rex = require "rex_pcre" + +local STUB_GET_URL = spec_helper.STUB_GET_URL +local PROXY_SSL_URL = spec_helper.PROXY_SSL_URL +local API_URL = spec_helper.API_URL + +local env = spec_helper.get_env() -- test environment +local dao_factory = env.dao_factory +local configuration = env.configuration +configuration.cassandra = configuration.databases_available[configuration.database].properties + +describe("OAuth2 Authentication Hooks", function() + + setup(function() + spec_helper.prepare_db() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + before_each(function() + spec_helper.restart_kong() + + spec_helper.drop_db() + spec_helper.insert_fixtures { + api = { + { request_host = "oauth2.com", upstream_url = "http://mockbin.com" } + }, + consumer = { + { username = "auth_tests_consumer" } + }, + plugin = { + { name = "oauth2", config = { scopes = { "email", "profile" }, mandatory_scope = true, provision_key = "provision123", token_expiration = 5, enable_implicit_grant = true }, __api = 1 } + }, + oauth2_credential = { + { client_id = "clientid123", client_secret = "secret123", redirect_uri = "http://google.com/kong", name="testapp", __consumer = 1 } + } + } + end) + + local function provision_code(client_id) + local response = http_client.post(PROXY_SSL_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = client_id, scope = "email", response_type = "code", state = "hello", authenticated_userid = "userid123" }, {host = "oauth2.com"}) + local body = json.decode(response) + if body.redirect_uri then + local matches = rex.gmatch(body.redirect_uri, "^http://google\\.com/kong\\?code=([\\w]{32,32})&state=hello$") + local code + for line in matches do + code = line + end + local data = dao_factory.oauth2_authorization_codes:find_by_keys({code = code}) + return data[1].code + end + end + + describe("OAuth2 Credentials entity invalidation", function() + it("should invalidate when OAuth2 Credential entity is deleted", function() + -- It should work + local code = provision_code("clientid123") + local _, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(200, status) + + -- Check that cache is populated + local cache_key = cache.oauth2_credential_key("clientid123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/auth_tests_consumer/oauth2/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete OAuth2 credential (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/auth_tests_consumer/oauth2/"..credential_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local code = provision_code("clientid123") + local _, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(400, status) + end) + it("should invalidate when OAuth2 Credential entity is updated", function() + -- It should work + local code = provision_code("clientid123") + local _, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(200, status) + + -- It should not work + local code = provision_code("updclientid123") + local _, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "updclientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(400, status) + + -- Check that cache is populated + local cache_key = cache.oauth2_credential_key("clientid123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/auth_tests_consumer/oauth2/") + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Update OAuth2 credential (which triggers invalidation) + local _, status = http_client.patch(API_URL.."/consumers/auth_tests_consumer/oauth2/"..credential_id, {client_id="updclientid123"}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should work + local code = provision_code("updclientid123") + local _, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "updclientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(200, status) + + -- It should not work + local code = provision_code("clientid123") + local _, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(400, status) + end) + end) + + describe("Consumer entity invalidation", function() + it("should invalidate when Consumer entity is deleted", function() + -- It should work + local code = provision_code("clientid123") + local _, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(200, status) + + -- Check that cache is populated + local cache_key = cache.oauth2_credential_key("clientid123") + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Delete Consumer (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/auth_tests_consumer") + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local code = provision_code("clientid123") + local _, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(400, status) + end) + end) + + describe("OAuth2 access token entity invalidation", function() + it("should invalidate when OAuth2 token entity is deleted", function() + -- It should work + local code = provision_code("clientid123") + local response, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(200, status) + local token = json.decode(response) + assert.truthy(token) + + local _, status = http_client.post(STUB_GET_URL, { access_token = token.access_token }, {host = "oauth2.com"}) + assert.are.equal(200, status) + + -- Check that cache is populated + local cache_key = cache.oauth2_token_key(token.access_token) + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Delete token (which triggers invalidation) + local res = dao_factory.oauth2_tokens:find_by_keys({access_token=token.access_token}) + local token_id = res[1].id + assert.truthy(token_id) + + local _, status = http_client.delete(API_URL.."/oauth2_tokens/"..token_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.post(STUB_GET_URL, { access_token = token.access_token }, {host = "oauth2.com"}) + assert.are.equal(403, status) + end) + it("should invalidate when Oauth2 token entity is updated", function() + -- It should work + local code = provision_code("clientid123") + local response, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(200, status) + local token = json.decode(response) + assert.truthy(token) + + local _, status = http_client.post(STUB_GET_URL, { access_token = token.access_token }, {host = "oauth2.com"}) + assert.are.equal(200, status) + + -- It should not work + local _, status = http_client.post(STUB_GET_URL, { access_token = "hello_token" }, {host = "oauth2.com"}) + assert.are.equal(403, status) + + -- Check that cache is populated + local cache_key = cache.oauth2_token_key(token.access_token) + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Update OAuth 2 token (which triggers invalidation) + local res = dao_factory.oauth2_tokens:find_by_keys({access_token=token.access_token}) + local token_id = res[1].id + assert.truthy(token_id) + + local _, status = http_client.patch(API_URL.."/oauth2_tokens/"..token_id, {access_token="hello_token"}) + assert.equals(200, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should work + local _, status = http_client.post(STUB_GET_URL, { access_token = "hello_token" }, {host = "oauth2.com"}) + assert.are.equal(200, status) + + -- It should not work + local _, status = http_client.post(STUB_GET_URL, { access_token = token.access_token }, {host = "oauth2.com"}) + assert.are.equal(403, status) + end) + end) + + describe("OAuth2 client entity invalidation", function() + it("should invalidate token when OAuth2 client entity is deleted", function() + -- It should work + local code = provision_code("clientid123") + local response, status = http_client.post(PROXY_SSL_URL.."/oauth2/token", { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" }, {host = "oauth2.com"}) + assert.are.equal(200, status) + local token = json.decode(response) + assert.truthy(token) + + local _, status = http_client.post(STUB_GET_URL, { access_token = token.access_token }, {host = "oauth2.com"}) + assert.are.equal(200, status) + + -- Check that cache is populated + local cache_key = cache.oauth2_token_key(token.access_token) + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve credential ID + local response, status = http_client.get(API_URL.."/consumers/auth_tests_consumer/oauth2/", {client_id="clientid123"}) + assert.equals(200, status) + local credential_id = table.remove(json.decode(response).data, 1).id + assert.truthy(credential_id) + + -- Delete OAuth2 client (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/consumers/auth_tests_consumer/oauth2/"..credential_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local _, status = http_client.post(STUB_GET_URL, { access_token = token.access_token }, {host = "oauth2.com"}) + assert.are.equal(403, status) + end) + end) + +end) diff --git a/spec/plugins/ssl/hooks_spec.lua b/spec/plugins/ssl/hooks_spec.lua new file mode 100644 index 00000000000..558311123a6 --- /dev/null +++ b/spec/plugins/ssl/hooks_spec.lua @@ -0,0 +1,166 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local cache = require "kong.tools.database_cache" +local ssl_fixtures = require "spec.plugins.ssl.fixtures" +local IO = require "kong.tools.io" +local url = require "socket.url" + +local STUB_GET_SSL_URL = spec_helper.STUB_GET_SSL_URL +local API_URL = spec_helper.API_URL + +describe("SSL Hooks", function() + + setup(function() + spec_helper.prepare_db() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + before_each(function() + spec_helper.restart_kong() + + spec_helper.drop_db() + spec_helper.insert_fixtures { + api = { + { request_host = "ssl1.com", upstream_url = "http://mockbin.com" } + }, + plugin = { + { name = "ssl", config = { cert = ssl_fixtures.cert, key = ssl_fixtures.key }, __api = 1 } + } + } + end) + + describe("SSL plugin entity invalidation", function() + it("should invalidate when SSL plugin is deleted", function() + -- It should work + local parsed_url = url.parse(STUB_GET_SSL_URL) + local res = IO.os_execute("(echo \"GET /\"; sleep 2) | openssl s_client -connect "..parsed_url.host..":"..tostring(parsed_url.port).." -servername ssl1.com") + + assert.truthy(res:match("US/ST=California/L=San Francisco/O=Kong/OU=IT/CN=ssl1.com")) + + -- Check that cache is populated + local response, status = http_client.get(API_URL.."/apis/", {request_host="ssl1.com"}) + assert.equals(200, status) + local api_id = table.remove(json.decode(response).data, 1).id + assert.truthy(api_id) + + local cache_key = cache.ssl_data(api_id) + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve SSL plugin + local response, status = http_client.get(API_URL.."/plugins/", {api_id=api_id, name="ssl"}) + assert.equals(200, status) + local plugin_id = table.remove(json.decode(response).data, 1).id + assert.truthy(plugin_id) + + -- Delete SSL plugin (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/plugins/"..plugin_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local parsed_url = url.parse(STUB_GET_SSL_URL) + local res = IO.os_execute("(echo \"GET /\"; sleep 2) | openssl s_client -connect "..parsed_url.host..":"..tostring(parsed_url.port).." -servername ssl1.com") + + assert.falsy(res:match("US/ST=California/L=San Francisco/O=Kong/OU=IT/CN=ssl1.com")) + end) + it("should invalidate when Basic Auth Credential entity is updated", function() + -- It should work + local parsed_url = url.parse(STUB_GET_SSL_URL) + local res = IO.os_execute("(echo \"GET /\"; sleep 2) | openssl s_client -connect "..parsed_url.host..":"..tostring(parsed_url.port).." -servername ssl1.com") + + assert.truthy(res:match("US/ST=California/L=San Francisco/O=Kong/OU=IT/CN=ssl1.com")) + + -- Check that cache is populated + local response, status = http_client.get(API_URL.."/apis/", {request_host="ssl1.com"}) + assert.equals(200, status) + local api_id = table.remove(json.decode(response).data, 1).id + assert.truthy(api_id) + + local cache_key = cache.ssl_data(api_id) + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Retrieve SSL plugin + local response, status = http_client.get(API_URL.."/plugins/", {api_id=api_id, name="ssl"}) + assert.equals(200, status) + local plugin_id = table.remove(json.decode(response).data, 1).id + assert.truthy(plugin_id) + + -- Update SSL plugin (which triggers invalidation) + local kong_working_dir = spec_helper.get_env(spec_helper.TEST_CONF_FILE).configuration.nginx_working_dir + local ssl_cert_path = IO.path:join(kong_working_dir, "ssl", "kong-default.crt") + local ssl_key_path = IO.path:join(kong_working_dir, "ssl", "kong-default.key") + + local res = IO.os_execute("curl -X PATCH -s -o /dev/null -w \"%{http_code}\" "..API_URL.."/apis/"..api_id.."/plugins/"..plugin_id.." --form \"config.cert=@"..ssl_cert_path.."\" --form \"config.key=@"..ssl_key_path.."\"") + assert.are.equal(200, tonumber(res)) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local parsed_url = url.parse(STUB_GET_SSL_URL) + local res = IO.os_execute("(echo \"GET /\"; sleep 2) | openssl s_client -connect "..parsed_url.host..":"..tostring(parsed_url.port).." -servername ssl1.com") + + assert.falsy(res:match("US/ST=California/L=San Francisco/O=Kong/OU=IT/CN=ssl1.com")) + assert.truthy(res:match("US/ST=California/L=San Francisco/O=Kong/OU=IT Department/CN=localhost")) + end) + end) + + describe("API entity invalidation", function() + it("should invalidate when API entity is deleted", function() + -- It should work + local parsed_url = url.parse(STUB_GET_SSL_URL) + local res = IO.os_execute("(echo \"GET /\"; sleep 2) | openssl s_client -connect "..parsed_url.host..":"..tostring(parsed_url.port).." -servername ssl1.com") + + assert.truthy(res:match("US/ST=California/L=San Francisco/O=Kong/OU=IT/CN=ssl1.com")) + + -- Check that cache is populated + local response, status = http_client.get(API_URL.."/apis/", {request_host="ssl1.com"}) + assert.equals(200, status) + local api_id = table.remove(json.decode(response).data, 1).id + assert.truthy(api_id) + + local cache_key = cache.ssl_data(api_id) + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + assert.equals(200, status) + + -- Delete API (which triggers invalidation) + local _, status = http_client.delete(API_URL.."/apis/"..api_id) + assert.equals(204, status) + + -- Wait for cache to be invalidated + local exists = true + while(exists) do + local _, status = http_client.get(API_URL.."/cache/"..cache_key) + if status ~= 200 then + exists = false + end + end + + -- It should not work + local parsed_url = url.parse(STUB_GET_SSL_URL) + local res = IO.os_execute("(echo \"GET /\"; sleep 2) | openssl s_client -connect "..parsed_url.host..":"..tostring(parsed_url.port).." -servername ssl1.com") + + assert.falsy(res:match("US/ST=California/L=San Francisco/O=Kong/OU=IT/CN=ssl1.com")) + end) + end) +end) diff --git a/spec/spec_helpers.lua b/spec/spec_helpers.lua index 90c2e3af4ee..15e67c04c2f 100644 --- a/spec/spec_helpers.lua +++ b/spec/spec_helpers.lua @@ -8,6 +8,7 @@ local dao = require "kong.tools.dao_loader" local Faker = require "kong.tools.faker" local Migrations = require "kong.tools.migrations" local Threads = require "llthreads2.ex" +local Events = require "kong.core.events" require "kong.tools.ngx_stub" @@ -33,10 +34,12 @@ _M.envs = {} -- a factory/migrations/faker that are environment-specific to this new config. function _M.add_env(conf_file) local env_configuration = config.load(conf_file) - local env_factory = dao.load(env_configuration) + local events = Events() + local env_factory = dao.load(env_configuration, events) _M.envs[conf_file] = { configuration = env_configuration, dao_factory = env_factory, + events = events, migrations = Migrations(env_factory), conf_file = conf_file, faker = Faker(env_factory) @@ -56,26 +59,19 @@ end -- -- OS and bin/kong helpers -- -local function kong_bin(signal, conf_file, skip_wait) +local function kong_bin(signal, conf_file) local env = _M.get_env(conf_file) local result, exit_code = IO.os_execute(_M.KONG_BIN.." "..signal.." -c "..env.conf_file) - if exit_code ~= 0 then error("spec_helper cannot "..signal.." kong: \n"..result) end - if signal == "start" and not skip_wait then - os.execute("while ! [ -f "..env.configuration.pid_file.." ]; do sleep 0; done") - elseif signal == "quit" or signal == "stop" then - os.execute("while [ -f "..env.configuration.pid_file.." ]; do sleep 0; done") - end - return result, exit_code end for _, signal in ipairs({ "start", "stop", "restart", "reload", "quit" }) do _M[signal.."_kong"] = function(conf_file, skip_wait) - return kong_bin(signal, conf_file, skip_wait) + return kong_bin(signal, conf_file) end end diff --git a/spec/unit/cli/utils_spec.lua b/spec/unit/cli/utils_spec.lua deleted file mode 100644 index 69e2a7a68ca..00000000000 --- a/spec/unit/cli/utils_spec.lua +++ /dev/null @@ -1,12 +0,0 @@ -local cutils = require "kong.cli.utils" -local spec_helper = require "spec.spec_helpers" - -describe("CLI Utils", function() - it("should check if a port is open", function() - local PORT = 30000 - assert.falsy(cutils.is_port_open(PORT)) - spec_helper.start_tcp_server(PORT, true, true) - os.execute("sleep 0") -- Wait for the server to start - assert.truthy(cutils.is_port_open(PORT)) - end) -end) diff --git a/spec/unit/rockspec_spec.lua b/spec/unit/rockspec_spec.lua new file mode 100644 index 00000000000..1cf37a87e83 --- /dev/null +++ b/spec/unit/rockspec_spec.lua @@ -0,0 +1,54 @@ +local spec_helper = require "spec.spec_helpers" +local constants = require "kong.constants" +local stringy = require "stringy" +local IO = require "kong.tools.io" +local fs = require "luarocks.fs" + +describe("Rockspec file", function() + + it("should include all the Lua modules", function() + local rockspec_path + for _, filename in ipairs(fs.list_dir(".")) do + if stringy.endswith(filename, "rockspec") then + rockspec_path = filename + break + end + end + if not rockspec_path then + error("Can't find the rockspec file") + end + + loadfile(rockspec_path)() + + -- Function that checks if the path has been imported as a module + local is_in_rockspec = function(path) + if stringy.startswith(path, "./") then + path = string.sub(path, 3) + end + local found = false + for _, v in pairs(build.modules) do + if v == path then + found = true + break + end + end + return found + end + + local res = IO.os_execute("find . -type f -name *.lua", true) + if not res or stringy.strip(res) == "" then + error("Error executing the command") + end + + local files = stringy.split(res, "\n") + for _, v in ipairs(files) do + local path = stringy.strip(v) + if path ~= "" and stringy.startswith(path, "./kong") then + if not is_in_rockspec(path) then + error("Module "..path.." is not declared in rockspec") + end + end + end + end) + +end) \ No newline at end of file diff --git a/spec/unit/tools/database_cache_spec.lua b/spec/unit/tools/database_cache_spec.lua index e24956443c7..4d331951795 100644 --- a/spec/unit/tools/database_cache_spec.lua +++ b/spec/unit/tools/database_cache_spec.lua @@ -3,22 +3,31 @@ local cache = require "kong.tools.database_cache" describe("Database cache", function() it("should return a valid API cache key", function() - assert.are.equal("apis/httpbin.org", cache.api_key("httpbin.org")) + assert.are.equal("apis:httpbin.org", cache.api_key("httpbin.org")) end) it("should return a valid PLUGIN cache key", function() - assert.are.equal("plugins/authentication/api123/app123", cache.plugin_key("authentication", "api123", "app123")) - assert.are.equal("plugins/authentication/api123", cache.plugin_key("authentication", "api123")) + assert.are.equal("plugins:authentication:api123:app123", cache.plugin_key("authentication", "api123", "app123")) + assert.are.equal("plugins:authentication:api123", cache.plugin_key("authentication", "api123")) end) it("should return a valid KeyAuthCredential cache key", function() - assert.are.equal("keyauth_credentials/username", cache.keyauth_credential_key("username")) + assert.are.equal("keyauth_credentials:username", cache.keyauth_credential_key("username")) end) it("should return a valid BasicAuthCredential cache key", function() - assert.are.equal("basicauth_credentials/username", cache.basicauth_credential_key("username")) + assert.are.equal("basicauth_credentials:username", cache.basicauth_credential_key("username")) end) + it("should return a valid HmacAuthCredential cache key", function() + assert.are.equal("hmacauth_credentials:username", cache.hmacauth_credential_key("username")) + end) + + it("should return a valid JWTAuthCredential cache key", function() + assert.are.equal("jwtauth_credentials:hello", cache.jwtauth_credential_key("hello")) + end) + + it("should return a valid requests cache key", function() assert.are.equal("requests", cache.requests_key()) end)