Control API: reload support.

A new /control/config endpoint provides configuration reload function
with feedback returned to the user on the outcome.

Example:

$ curl 127.0.0.1:1234/1/control/config -X PATCH -v | jq
< HTTP/1.1 200 OK
< ...
<
{
  "logs": []
}

$ curl 127.0.0.1:1234/1/control/config -X PATCH -v | jq
< HTTP/1.1 422 Unprocessable Entity
< ...
<
{
  "logs": [
  "...: invalid number of arguments in \"listen\" directive"
  ]
}

Co-authored-by: Pavel Pautov <p.pautov@f5.com>
This commit is contained in:
Ava Hahn 2025-12-30 19:35:18 +00:00 • committed by Ava Hahn
parent 4bc459a15c
commit 74133fba7c
2 changed files with 153 additions and 5 deletions

View file

@ -36,6 +36,8 @@ typedef struct {
ngx_buf_t *in;
ngx_chain_t *out;
ngx_uint_t reloaded;
} ngx_control_request_t;
@ -60,6 +62,9 @@ static ngx_int_t ngx_control_api_endpoints(ngx_control_request_t *r,
const char **paths, ngx_int_t npaths);
static ngx_int_t ngx_control_show_processes(ngx_control_request_t *r);
static ngx_int_t ngx_control_show_version(ngx_control_request_t *r);
static ngx_int_t ngx_control_reload_config(ngx_control_request_t *r);
static void ngx_control_log_capture(ngx_log_t *log, ngx_uint_t l,
u_char *buf, size_t len);
static ngx_int_t ngx_control_parse_request_line(ngx_control_request_t *r);
static ngx_int_t ngx_control_send_json(ngx_control_request_t *r, ngx_int_t code,
@ -288,7 +293,7 @@ ngx_int_t
ngx_control_handle_events(void)
{
int ready, revents;
ngx_uint_t i;
ngx_uint_t i, reloaded;
struct pollfd *pfd;
ngx_control_request_t *r;
@ -307,6 +312,8 @@ ngx_control_handle_events(void)
return NGX_OK;
}
reloaded = 0;
for (i = 0; i < ngx_control_pollfd_n; /* void */) {
r = &ngx_control_requests[i];
@ -317,6 +324,10 @@ ngx_control_handle_events(void)
"control: i:%ui fd:%d e:%d re:%d",
i, pfd->fd, pfd->events, pfd->revents);
if (i) {
r->pool->log = ngx_cycle->log;
}
if ((revents & (POLLERR|POLLHUP|POLLNVAL)) && i != 0) {
ngx_control_close(r);
continue;
@ -330,9 +341,12 @@ ngx_control_handle_events(void)
}
if (ngx_control_handle_read(r) != NGX_AGAIN) {
reloaded |= r->reloaded;
ngx_control_close(r);
continue;
}
reloaded |= r->reloaded;
}
if (revents & POLLOUT) {
@ -345,7 +359,7 @@ ngx_control_handle_events(void)
i++;
}
return NGX_OK;
return reloaded ? NGX_DONE : NGX_OK;
}
@ -562,7 +576,7 @@ ngx_control_content(ngx_control_request_t *r)
static const char *root_endpoints[] = { "1" };
static const char *api_endpoints[] = { "control", "nginx" };
static const char *control_endpoints[] = { "processes" };
static const char *control_endpoints[] = { "processes", "config" };
p = r->path.data;
len = r->path.len;
@ -609,7 +623,18 @@ ngx_control_content(ngx_control_request_t *r)
return ngx_control_send_response(r, NGX_CTRL_NOT_ALLOWED, NULL);
}
return ngx_control_api_endpoints(r, control_endpoints, 1);
return ngx_control_api_endpoints(r, control_endpoints, 2);
}
break;
case 17:
if (ngx_strncmp(p, "/1/control/config", len) == 0) {
if (r->method != NGX_CTRL_PATCH) {
return ngx_control_send_response(r, NGX_CTRL_NOT_ALLOWED, NULL);
}
return ngx_control_reload_config(r);
}
break;
@ -836,6 +861,115 @@ ngx_control_show_version(ngx_control_request_t *r)
}
static ngx_data_item_t *
ngx_control_json_logs(ngx_array_t *logs)
{
ngx_str_t *l;
ngx_uint_t i;
ngx_data_item_t *obj, *log_list, *log;
static ngx_str_t logs_field = ngx_string("logs");
obj = ngx_data_new_object(logs->pool);
if (obj == NULL) {
return NULL;
}
log_list = ngx_data_new_list(logs->pool);
if (log_list == NULL) {
return NULL;
}
l = logs->elts;
for (i = 0; i < logs->nelts; i++) {
log = ngx_data_string_handler((uintptr_t) &l[i], logs->pool, NULL);
if (log == NULL) {
return NULL;
}
ngx_data_add_item(log_list, NULL, log);
}
ngx_data_add_item(obj, &logs_field, log_list);
return obj;
}
static ngx_int_t
ngx_control_reload_config(ngx_control_request_t *r)
{
ngx_int_t status;
ngx_log_t log, *tmp;
ngx_cycle_t *cycle;
ngx_array_t log_store;
ngx_data_item_t *obj;
status = NGX_CTRL_OK;
if (ngx_array_init(&log_store, r->pool, 3, sizeof(ngx_str_t)) != NGX_OK) {
return NGX_ERROR;
}
ngx_memzero(&log, sizeof(ngx_log_t));
log.wdata = &log_store;
log.writer = ngx_control_log_capture;
log.log_level = NGX_LOG_DEBUG;
tmp = ngx_cycle->log;
log.next = tmp;
ngx_cycle->log = &log;
cycle = ngx_init_cycle((ngx_cycle_t *) ngx_cycle);
if (cycle == NULL) {
status = NGX_CTRL_UNPROCESSABLE;
ngx_cycle->log = tmp;
} else {
ngx_cycle = cycle;
r->reloaded = 1;
r->pool->log = ngx_cycle->log;
}
obj = ngx_control_json_logs(&log_store);
if (obj == NULL) {
return NGX_ERROR;
}
return ngx_control_send_json(r, status, obj);
}
static void
ngx_control_log_capture(ngx_log_t *log, ngx_uint_t lvl, u_char *buf, size_t len)
{
u_char *log_data;
ngx_str_t *log_str;
ngx_array_t *store;
if (lvl > NGX_LOG_WARN) {
return;
}
store = log->wdata;
log_data = ngx_pnalloc(store->pool, len);
if (log_data == NULL) {
return;
}
log_str = ngx_array_push(store);
if (log_str == NULL) {
return;
}
ngx_memcpy(log_data, buf, len);
log_str->data = log_data;
log_str->len = len;
}
static ngx_int_t
ngx_control_parse_request_line(ngx_control_request_t *r)
{

View file

@ -178,7 +178,21 @@ ngx_master_process_cycle(ngx_cycle_t *cycle)
if (ngx_sigio) {
ngx_sigio = 0;
ngx_control_handle_events();
if (ngx_control_handle_events() == NGX_DONE) {
cycle = (ngx_cycle_t *) ngx_cycle;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_core_module);
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_JUST_RESPAWN);
ngx_start_cache_manager_processes(cycle, 1);
/* allow new processes to start */
ngx_msleep(100);
live = 1;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
}
#endif