mirror of
https://github.com/mailscope/kumomta.git
synced 2026-09-13 14:02:13 +00:00
Refine the egress path update logic as part of that, so that we wake sleeping Dispatchers to have them notice when it changes.
235 lines
6.0 KiB
Lua
235 lines
6.0 KiB
Lua
local mod = {}
|
|
local kumo = require 'kumo'
|
|
local utils = require 'policy-extras.policy_utils'
|
|
|
|
--[[
|
|
Usage:
|
|
|
|
Create a `/opt/kumomta/etc/shaping.json` with contents like:
|
|
|
|
```json
|
|
{
|
|
"gmail.com": {
|
|
// "mx_rollup": false, // if false, is NOT shared with other domains with same mx
|
|
"connection_limit": 3,
|
|
|
|
// Entries that are themselves objects are considered to be
|
|
// source configuration. These will take precendence over
|
|
// the more general configuration for the domain/site
|
|
"sources": {
|
|
"source-0": {
|
|
"connection_limit": 5
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
local shaping = require 'policy-extras.shaping'
|
|
kumo.on('get_egress_path_config', shaping:setup('/opt/kumomta/etc/shaping.json'))
|
|
---
|
|
|
|
Alternatively, you could use a TOML file instead.
|
|
]]
|
|
|
|
function mod:setup(extra_files)
|
|
return self:setup_with_automation({
|
|
extra_files = extra_files,
|
|
publish = {},
|
|
subscribe = {},
|
|
}).get_egress_path_config
|
|
end
|
|
|
|
local function load_shaping_data(file_names)
|
|
-- print('Loading shaping data from ', kumo.json_encode(file_names))
|
|
local result = kumo.shaping.load(file_names)
|
|
local warnings = result:get_warnings()
|
|
for _, warn in ipairs(warnings) do
|
|
print(warn)
|
|
end
|
|
-- print('Computed', kumo.json_encode_pretty(result))
|
|
return result
|
|
end
|
|
|
|
local function should_enq(publish, msg, hook_name)
|
|
local params = publish[hook_name]
|
|
if not params then
|
|
-- User defined log hook that is not part of shaping.lua
|
|
return false
|
|
end
|
|
|
|
local log_record = msg:get_meta 'log_record'
|
|
|
|
-- We only want to log if the event isn't one of our
|
|
-- publishing events
|
|
for name, _ in pairs(publish) do
|
|
if name == log_record.queue then
|
|
-- It's one of our log hooks; don't queue this one
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- It was not destined to any of our hooks, so we can safely
|
|
-- queue this one without triggering a cycle
|
|
msg:set_meta('queue', hook_name)
|
|
return true
|
|
end
|
|
|
|
local function construct_publisher(publish, domain)
|
|
local connection = {}
|
|
local client = kumo.http.build_client {}
|
|
function connection:send(message)
|
|
local response = client
|
|
:post(string.format('%s/publish_log_v1', publish.endpoint))
|
|
:header('Content-Type', 'application/json')
|
|
:body(message:get_data())
|
|
:send()
|
|
|
|
local disposition = string.format(
|
|
'%d %s: %s',
|
|
response:status_code(),
|
|
response:status_reason(),
|
|
response:text()
|
|
)
|
|
|
|
if response:status_is_success() then
|
|
return disposition
|
|
end
|
|
|
|
-- retry later
|
|
kumo.reject(400, disposition)
|
|
end
|
|
return connection
|
|
end
|
|
|
|
local function get_queue_cfg(publish, domain, tenant, campaign)
|
|
for _, data in pairs(publish) do
|
|
if data.hook_name == domain then
|
|
return kumo.make_queue_config {
|
|
protocol = {
|
|
custom_lua = {
|
|
constructor = data.constructor,
|
|
},
|
|
},
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[
|
|
local shaper = shaping:setup_with_automation {
|
|
publish = {"http://10.0.0.1:8008"},
|
|
subscribe = {"http://10.0.0.1:8008"},
|
|
}
|
|
|
|
kumo.on('init', function()
|
|
shaper.setup_publish()
|
|
end)
|
|
|
|
kumo.on('get_egress_path_config', shaper.get_egress_path_config)
|
|
kumo.on('should_enqueue_log_record', shaper.should_enqueue_log_record)
|
|
kumo.on('get_queue_config', function(domain, tenant, campaign)
|
|
local cfg = shaper.get_queue_config(domain, tenant, campaign)
|
|
if cfg then
|
|
return cfg
|
|
end
|
|
|
|
-- Do your normal queue config handling here
|
|
end)
|
|
]]
|
|
function mod:setup_with_automation(options)
|
|
local cached_load_data = kumo.memoize(load_shaping_data, {
|
|
name = 'shaping_data',
|
|
ttl = '1 minute',
|
|
capacity = 10,
|
|
})
|
|
|
|
local file_names = {
|
|
-- './assets/policy-extras/shaping.toml',
|
|
'/opt/kumomta/share/policy-extras/shaping.toml',
|
|
}
|
|
if options.extra_files then
|
|
for _, filename in ipairs(options.extra_files) do
|
|
table.insert(file_names, filename)
|
|
end
|
|
end
|
|
if options.subscribe then
|
|
for _, url in ipairs(options.subscribe) do
|
|
table.insert(
|
|
file_names,
|
|
string.format('%s/get_config_v1/shaping.toml', url)
|
|
)
|
|
end
|
|
end
|
|
|
|
local publish = {}
|
|
for _, destination in ipairs(options.publish) do
|
|
-- Generate the hook name and constructor name and
|
|
-- keep that info in a more structured form
|
|
local hook_name = string.format('%s.tsa.kumomta', destination)
|
|
local constructor = string.format('make.%s', hook_name)
|
|
|
|
publish[hook_name] = {
|
|
endpoint = destination,
|
|
hook_name = hook_name,
|
|
constructor = constructor,
|
|
}
|
|
|
|
-- Since we own naming the constructor events, we can make
|
|
-- them unique without fear of colliding with user-provided
|
|
-- events, so we can simply bind the event handlers here
|
|
-- without returning them to the caller to deal with
|
|
kumo.on(constructor, function(domain, _tenant, _campaign)
|
|
return construct_publisher(publish[hook_name], domain)
|
|
end)
|
|
end
|
|
|
|
local function setup_publish()
|
|
for _, params in pairs(publish) do
|
|
kumo.configure_log_hook {
|
|
name = params.hook_name,
|
|
per_record = {
|
|
-- Don't feed reception data to the daemon; we're
|
|
-- only interested in data that flows back to us
|
|
-- from after the point of reception
|
|
Reception = {
|
|
enable = false,
|
|
},
|
|
},
|
|
}
|
|
end
|
|
end
|
|
|
|
local function get_egress_path_config(domain, egress_source, site_name)
|
|
local data = cached_load_data(file_names, options.subscribe)
|
|
local params =
|
|
data:get_egress_path_config(domain, egress_source, site_name)
|
|
|
|
--[[
|
|
print(
|
|
'going to make egress path',
|
|
domain,
|
|
egress_source,
|
|
site_name,
|
|
kumo.json_encode(params)
|
|
)
|
|
]]
|
|
|
|
return kumo.make_egress_path(params)
|
|
end
|
|
|
|
return {
|
|
get_egress_path_config = get_egress_path_config,
|
|
should_enqueue_log_record = function(msg, hook_name)
|
|
return should_enq(publish, msg, hook_name)
|
|
end,
|
|
setup_publish = setup_publish,
|
|
get_queue_config = function(domain, tenant, campaign)
|
|
return get_queue_cfg(publish, domain, tenant, campaign)
|
|
end,
|
|
}
|
|
end
|
|
|
|
return mod
|