Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: support to use plugin_config_id for consumer object #5800

Open
membphis opened this issue Dec 14, 2021 · 3 comments
Open

feat: support to use plugin_config_id for consumer object #5800

membphis opened this issue Dec 14, 2021 · 3 comments
Labels
enhancement New feature or request

Comments

@membphis
Copy link
Member

Issue description

Now, the router object supports the plugin_config_id, here is the doc link: https://github.com/apache/apisix/blob/master/docs/en/latest/admin-api.md#route

And I think we need to support plugin_config_id for consumer object.

What do you think?

Environment

  • all version
@membphis membphis added the enhancement New feature or request label Dec 14, 2021
@kaori-seasons
Copy link

kaori-seasons commented Apr 28, 2022

@membphis

Issue description
https://www.processon.com/view/link/626a5b656376891e1c158aad
At present, the configuration items of the nginx file of apisix and the processing flow of APISIX are shown in the following figure

apisix process

In the calling phase of the ``http_access_phase```` method, the relevant upstream id will be obtained to split the sent data.
And in the init.lua method, the following code will appear

init.lua

function _M.http_access_phase()

    local ngx_ctx = ngx.ctx

    if not verify_tls_client(ngx_ctx.api_ctx) then
        return core.response.exit(400)
    end

    -- always fetch table from the table pool, we don't need a reused api_ctx
    local api_ctx = core.tablepool.fetch("api_ctx", 0, 32)
    ngx_ctx.api_ctx = api_ctx
     local up_id = route.value.upstream_id

    -- used for the traffic-split plugin
    if api_ctx.upstream_id then
        up_id = api_ctx.upstream_id
    end

    if up_id then
 >     local upstream = get_upstream_by_id(up_id)
        api_ctx.matched_upstream = upstream

end

local function get_upstream_by_id(up_id)
    local upstreams = core.config.fetch_created_obj("/upstreams")
    if upstreams then
        local upstream = upstreams:get(tostring(up_id))
        if not upstream then
            core.log.error("failed to find upstream by id: " .. up_id)
            if is_http then
                return core.response.exit(502)
            end

            return ngx_exit(1)
        end

        if upstream.has_domain then
            local err
            upstream, err = parse_domain_in_up(upstream)
            if err then
                core.log.error("failed to get resolved upstream: ", err)
                if is_http then
                    return core.response.exit(500)
                end

                return ngx_exit(1)
            end
        end

        core.log.info("parsed upstream: ", core.json.delay_encode(upstream, true))
        return upstream.dns_value or upstream.value
    end
end

To understand how router.lua is called based on upstream_id, I traced the following code.
In the request process of Route's admin api:

local function filter(route)
    route.orig_modifiedIndex = route.modifiedIndex
    route.update_count = 0

    route.has_domain = false
    if not route.value then
        return
    end

    if route.value.host then
        route.value.host = str_lower(route.value.host)
    elseif route.value.hosts then
        for i, v in ipairs(route.value.hosts) do
            route.value.hosts[i] = str_lower(v)
        end
    end

    apisix_upstream.filter_upstream(route.value.upstream, route)

    core.log.info("filter route: ", core.json.delay_encode(route, true))

To sum up, I think that consumer.lua should do some attribute extraction before calling the apisix_upstream.filter_upstream method and then execute the filter. So based on this idea, I decided to follow up the related call link of route call /upstream

I would like to know suppose now that I want to configure a set of admin api as shown below, how should I do the relevant verification on the consumer side to complete the connectivity test of calling /upstream from the route /consumer?

location /apisix/admin {
            set $upstream_scheme             'http';
            set $upstream_host               $http_host;
            set $upstream_uri                '';

                allow 127.0.0.0/24;
                deny all;

            content_by_lua_block {
                apisix.http_admin()
            }
        }

@tzssangglass
Copy link
Member

Hi @complone , from your trace path, I think you have missed the key point, APISIX merge the consumer's plugins and route's plugins is here:

apisix/apisix/plugin.lua

Lines 491 to 513 in 4afc8a7

local function merge_consumer_route(route_conf, consumer_conf)
if not consumer_conf.plugins or
core.table.nkeys(consumer_conf.plugins) == 0
then
core.log.info("consumer no plugins")
return route_conf
end
local new_route_conf = core.table.deepcopy(route_conf)
for name, conf in pairs(consumer_conf.plugins) do
if not new_route_conf.value.plugins then
new_route_conf.value.plugins = {}
end
if new_route_conf.value.plugins[name] == nil then
conf._from_consumer = true
end
new_route_conf.value.plugins[name] = conf
end
core.log.info("merged conf : ", core.json.delay_encode(new_route_conf))
return new_route_conf
end

@kaori-seasons
Copy link

kaori-seasons commented Apr 29, 2022

@tzssangglass

I understand that I should define the relevant plugin_config_id in shcema_def.lua, and then set the relevant plugin_config_id in consumer.lua#plugin_consumer

shcema_def.lua

_M.consumer = {
    type = "object",
    properties = {
        username = {
            type = "string", minLength = 1, maxLength = rule_name_def.maxLength,
            pattern = [[^[a-zA-Z0-9_]+$]]
        },
  >    id = id_schema,
        plugins = plugins_schema,
        labels = labels_def,
        create_time = timestamp_def,
        update_time = timestamp_def,
        desc = desc_def,
    },
    required = {"username"},
}

consumer.lua

local function plugin_consumer()
    local plugins = {}

    if consumers.values == nil then
        return plugins
    end

    for _, consumer in ipairs(consumers.values) do
        if type(consumer) ~= "table" then
            goto CONTINUE
        end

        for name, config in pairs(consumer.value.plugins or {}) do
            local plugin_obj = plugin.get(name)
            if plugin_obj and plugin_obj.type == "auth" then
                if not plugins[name] then
                    plugins[name] = {
                        nodes = {},
                        conf_version = consumers.conf_version
                    }
                end

                local new_consumer = core.table.clone(consumer.value)
                -- Note: the id here is the key of consumer data, which
                -- is 'username' field in admin
          >     new_consumer.id = consumer.value.id

                new_consumer.consumer_name = new_consumer.id
                new_consumer.auth_conf = config
                core.log.info("consumer:", core.json.delay_encode(new_consumer))
                core.table.insert(plugins[name].nodes, new_consumer)
            end
        end

        ::CONTINUE::
    end

    return plugins
end

I will start testing the relevant request processing link in the near future

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants