Files
kumomta/crates/integration-tests/source.lua
T
Wez Furlong 979e69fb8c add min_batch_size and max_batch_latency to lua delivery handler
Previously, we added `batch_size` which acts as a maximum batch
size; this was the simplest possible way to achieve batching
with a constrained upper bound with no additional latency.

This commit introduces a minimum batch size and a maximum latency.
The behavior for delivery handlers that configure min_batch_size > 1
is that once we've started a batch, we'll allow for up to
max_batch_latency time to pass to accumulate more messages into
the batch before we just go ahead and send it.

If you wanted to batch say 5,000-10,000 messages per batch or whatever
you get in a 10 second time period you would set:

* min_batch_size = 5000
* batch_size = 10000
* max_batch_latency = "10s"

Note that the default idle_timeout in the product is "5s" so if you want
max_batch_latency to be effective you will also need to increase the
idle_timeout in your shaping configuration/egress path config for that
handler.
2024-11-14 08:54:14 -07:00

284 lines
7.5 KiB
Lua

local kumo = require 'kumo'
package.path = '../../assets/?.lua;' .. package.path
log_hooks = require 'policy-extras.log_hooks'
local TEST_DIR = os.getenv 'KUMOD_TEST_DIR'
local SINK_PORT = tonumber(os.getenv 'KUMOD_SMTP_SINK_PORT')
local WEBHOOK_PORT = os.getenv 'KUMOD_WEBHOOK_PORT'
local AMQPHOOK_URL = os.getenv 'KUMOD_AMQPHOOK_URL'
local AMQP_HOST_PORT = os.getenv 'KUMOD_AMQP_HOST_PORT'
local LISTENER_MAP = os.getenv 'KUMOD_LISTENER_DOMAIN_MAP'
kumo.on('init', function()
kumo.configure_accounting_db_path(TEST_DIR .. '/accounting.db')
local relay_hosts = { '0.0.0.0/0' }
local RELAY_HOSTS = os.getenv 'KUMOD_RELAY_HOSTS'
if RELAY_HOSTS then
relay_hosts = kumo.json_parse(RELAY_HOSTS)
end
kumo.start_esmtp_listener {
listen = '127.0.0.1:0',
relay_hosts = relay_hosts,
}
kumo.start_http_listener {
listen = '127.0.0.1:0',
}
kumo.configure_local_logs {
log_dir = TEST_DIR .. '/logs',
max_segment_duration = '1s',
headers = { 'X-*', 'Y-*', 'Subject' },
}
if WEBHOOK_PORT then
kumo.configure_log_hook {
name = 'webhook',
headers = { 'Subject', 'X-*' },
}
end
kumo.define_spool {
name = 'data',
path = TEST_DIR .. '/data-spool',
}
kumo.define_spool {
name = 'meta',
path = TEST_DIR .. '/meta-spool',
}
end)
if AMQPHOOK_URL then
log_hooks:new {
name = 'amqp',
constructor = function(domain, tenant, campaign)
local sender = {}
local client = kumo.amqp.build_client(AMQPHOOK_URL)
function sender:send(msg)
local result = client:publish_with_timeout({
routing_key = 'woot',
payload = msg:get_data(),
}, 20000)
if result.status == 'Ack' or result.status == 'NotRequested' then
return string.format('250 %s', kumo.json_encode(result))
end
-- result.status must be `Nack`; log the full result
kumo.reject(500, kumo.json_encode(result))
end
function sender:close()
client:close()
end
return sender
end,
}
elseif AMQP_HOST_PORT then
log_hooks:new {
name = 'amqp',
constructor = function(domain, tenant, campaign)
local sender = {}
local host, port = table.unpack(kumo.string.split(AMQP_HOST_PORT, ':'))
function sender:send(msg)
kumo.amqp.basic_publish {
routing_key = 'woot',
payload = msg:get_data(),
connection = {
host = host,
port = tonumber(port),
},
}
return '250 ok'
end
return sender
end,
}
end
if WEBHOOK_PORT then
local min_batch_size = tonumber(os.getenv 'KUMOD_WEBHOOK_MIN_BATCH_SIZE')
local max_batch_size = tonumber(os.getenv 'KUMOD_WEBHOOK_MAX_BATCH_SIZE')
local max_batch_latency = os.getenv 'KUMOD_WEBHOOK_MAX_BATCH_LATENCY'
if max_batch_size > 1 then
log_hooks:new {
name = 'webhookbatch',
batch_size = max_batch_size,
min_batch_size = min_batch_size,
max_batch_latency = max_batch_latency,
constructor = function(domain, tenant, campaign)
local sender = {}
local client = kumo.http.build_client {}
function sender:send_batch(messages)
local payload = {}
for _, msg in ipairs(messages) do
table.insert(payload, msg:get_meta 'log_record')
end
print(
string.format(
'batch size is %d *************** min=%d max=%d latency=%s',
#payload,
min_batch_size,
max_batch_size,
max_batch_latency
)
)
local response = client
:post(
string.format('http://127.0.0.1:%d/log-batch', WEBHOOK_PORT)
)
:header('Content-Type', 'application/json')
:body(kumo.serde.json_encode(payload))
: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
kumo.reject(500, disposition)
end
return sender
end,
}
else
kumo.on('should_enqueue_log_record', function(msg)
local log_record = msg:get_meta 'log_record'
-- avoid an infinite loop caused by logging that we logged
if log_record.queue ~= 'webhook' then
msg:set_meta('queue', 'webhook')
return true
end
return false
end)
kumo.on('make.webhook', function(_domain, _tenant, _campaign)
local sender = {}
local client = kumo.http.build_client {}
function sender:send(message)
local response = client
:post(string.format('http://127.0.0.1:%d/log', WEBHOOK_PORT))
: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
kumo.reject(500, disposition)
end
return sender
end)
end
end
kumo.on('get_listener_domain', function(domain, listener, conn_meta)
if LISTENER_MAP then
local map = kumo.json_parse(LISTENER_MAP)
local params = map[domain]
if params then
return kumo.make_listener_domain(params)
end
end
return kumo.make_listener_domain {
relay_to = true,
log_oob = true,
log_arf = true,
}
end)
kumo.on('smtp_server_message_received', function(msg) end)
kumo.on('get_queue_config', function(domain, _tenant, _campaign)
if domain == 'webhook' then
return kumo.make_queue_config {
protocol = {
custom_lua = {
constructor = 'make.webhook',
},
},
}
end
local protocol = {
-- Redirect traffic to the sink
smtp = {
mx_list = { 'localhost' },
},
}
if domain == 'nxdomain' then
-- this nxdomain domain is a special domain that is assumed not
-- to resolve. It is generated by the retry_schedule integration
-- test. for this domain, we don't want to short-circuit dns
-- and go to the sink, because we DO want the dns resolution
-- to successfully return nxdomain in order for the test to
-- exercise the appropriate logic.
protocol = nil
end
return kumo.make_queue_config {
protocol = protocol,
retry_interval = os.getenv 'KUMOD_RETRY_INTERVAL',
strategy = os.getenv 'KUMOD_QUEUE_STRATEGY',
}
end)
kumo.on('get_egress_path_config', function(domain, _source_name, _site_name)
-- Allow sending to a sink
local params = {
enable_tls = os.getenv 'KUMOD_ENABLE_TLS' or 'OpportunisticInsecure',
smtp_port = SINK_PORT,
prohibited_hosts = {},
}
local username = os.getenv 'KUMOD_SMTP_AUTH_USERNAME'
local password = os.getenv 'KUMOD_SMTP_AUTH_PASSWORD'
if username and password then
params.smtp_auth_plain_username = username
params.smtp_auth_plain_password = {
key_data = password,
}
end
if domain == 'webhookbatch.log_hook' then
-- If we allow more connections, then we can end up
-- with batches smaller than desired because we split
-- them among multiple connections
params.connection_limit = 1
end
print('get_egress_path_config *******************', domain)
return kumo.make_egress_path(params)
end)
if os.getenv 'KUMOD_WANT_REBIND' then
kumo.on('rebind_message', function(message, data)
message:set_meta('queue', data.queue)
end)
end