/*
* Does health checks of servers in an upstream
*
* Author: Jack Lindamood <jack facebook com>
*
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_http_healthcheck_module.h>
#ifdef NGX_SUPERVISORD_MODULE
#include <ngx_supervisord.h>
#if (NGX_SUPERVISORD_API_VERSION != 2)
#error "ngx_http_upstream_fair_module requires NGX_SUPERVISORD_API v2"
#endif
#endif
#if (!NGX_HAVE_ATOMIC_OPS)
#error "Healthcheck module only works with atomic ops"
#endif
typedef enum {
// In progress states
NGX_HEALTH_UNINIT_STATE = 0,
NGX_HEALTH_WAITING,
NGX_HEALTH_SENDING_CHECK,
NGX_HEALTH_READING_STAT_LINE,
NGX_HEALTH_READING_STAT_CODE,
NGX_HEALTH_READING_HEADER,
NGX_HEALTH_HEADER_ALMOST_DONE,
NGX_HEALTH_READING_BODY,
// Good + final states
NGX_HEALTH_OK = 100,
// bad + final states
NGX_HEALTH_BAD_HEADER = 200,
NGX_HEALTH_BAD_STATUS,
NGX_HEALTH_BAD_BODY,
NGX_HEALTH_BAD_STATE,
NGX_HEALTH_BAD_CONN,
NGX_HEALTH_BAD_CODE,
NGX_HEALTH_TIMEOUT,
NGX_HEALTH_FULL_BUFFER,
NGX_HEALTH_EARLY_CLOSE
} ngx_http_health_state;
typedef struct {
// Worker pid processing this healthcheck
ngx_pid_t owner;
// matches the non shared memory index
ngx_uint_t index;
// Last time any action (read/write/timeout) was taken on this structure
ngx_msec_t action_time;
// Number of concurrent bad or good responses
ngx_int_t concurrent;
// How long this server's been concurrently bad or good
ngx_msec_t since;
// If true, the server's last response was bad
unsigned last_down:1;
// Code (above ngx_http_health_state) of last finished check
ngx_http_health_state down_code;
// Used so multiple processes don't try to healthcheck the same peer
ngx_atomic_t lock;
/**
* If true, the server is actually down. This is
* different than last_down because a server needs
* X concurrent good or bad connections to actually
* be down
*/
ngx_atomic_t down;
} ngx_http_healthcheck_status_shm_t;
typedef struct {
// Upstream this peer belongs to
ngx_http_upstream_srv_conf_t *conf;
// The peer to check
#if defined(nginx_version) && nginx_version >= 8022
ngx_addr_t *peer;
#else
ngx_peer_addr_t *peer;
#endif
// Index of the peer. Matches shm segment and is used for 'down' checking
// by external clients
ngx_uint_t index;
// Current state of the healthcheck. Different than shm->down_state
// because this is an active state and that is a finisehd state.
ngx_http_health_state state;
// Connection to the peer. We reuse this memory each healthcheck, but
// memset zero it
ngx_peer_connection_t *pc;
// When the check began so we can diff it with action_time and time the
// check out
ngx_msec_t check_start_time;
// Event that triggers a health check
ngx_event_t health_ev;
// Event that triggers an attempt at ownership of this healthcheck
ngx_event_t ownership_ev;
ngx_buf_t *read_buffer;
// Where I am reading the entire connection, headers + body
ssize_t read_pos;
// Where I am in conf->health_expected (the body only)
ssize_t body_read_pos;
// Where I am in conf->health_send
ssize_t send_pos;
// HTTP status code returned (200, 404, etc)
ngx_uint_t stat_code;
ngx_http_healthcheck_status_shm_t *shm;
} ngx_http_healthcheck_status_t;
// This one is not shared. Created when the config is parsed
static ngx_array_t *ngx_http_healthchecks_arr;
// This is the same as the above data ->elts. For ease of use
#define ngx_http_healthchecks \
((ngx_http_healthcheck_status_t*) ngx_http_healthchecks_arr->elts)
static ngx_http_healthcheck_status_shm_t *ngx_http_healthchecks_shm;
static ngx_int_t ngx_http_healthcheck_init(ngx_conf_t *cf);
static char* ngx_http_healthcheck_enabled(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char* ngx_http_healthcheck_delay(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char* ngx_http_healthcheck_timeout(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char* ngx_http_healthcheck_failcount(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char* ngx_http_healthcheck_send(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char* ngx_http_healthcheck_expected(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char* ngx_http_healthcheck_buffer(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char* ngx_http_set_healthcheck_status(ngx_conf_t *cf, ngx_command_t *cmd,
void*conf);
static ngx_int_t ngx_http_healthcheck_procinit(ngx_cycle_t *cycle);
static ngx_int_t ngx_http_healthcheck_preconfig(ngx_conf_t *cf);
static ngx_int_t ngx_http_healthcheck_init_zone(ngx_shm_zone_t *shm_zone,
void *data);
static ngx_int_t ngx_http_healthcheck_process_recv(
ngx_http_healthcheck_status_t *stat);
static char* ngx_http_healthcheck_statestr(
ngx_http_health_state state);
// I really wish there was a way to make nginx call this when you HUP the
// master
void ngx_http_healthcheck_clear_events(ngx_log_t *log);
static ngx_command_t ngx_http_healthcheck_commands[] = {
/**
* If mentioned, enable healthchecks for this upstream
*/
{ ngx_string("healthcheck_enabled"),
NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
ngx_http_healthcheck_enabled,
0,
0,
NULL },
/**
* Delay in msec between healthchecks for a single peer
*/
{ ngx_string("healthcheck_delay"),
NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
ngx_http_healthcheck_delay,
0,
0,
NULL } ,
/**
* How long in msec a healthcheck is allowed to take place
*/
{ ngx_string("healthcheck_timeout"),
NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
ngx_http_healthcheck_timeout,
0,
0,
NULL },
/**
* Number of healthchecks good or bad in a row it takes to switch from
* down to up and back. Good to prevent flapping
*/
{ ngx_string("healthcheck_failcount"),
NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
ngx_http_healthcheck_failcount,
0,
0,
NULL } ,
/**
* What to send for the healthcheck. Each argument is appended by \r\n
* and the entire thing is suffixed with another \r\n. For example,
*
* healthcheck_send 'GET /health HTTP/1.1'
* 'Host: www.facebook.com' 'Connection: close';
*
* Note that you probably want to end your health check with some directive
* that closes the connection, like Connection: close.
*
*/
{ ngx_string("healthcheck_send"),
NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
ngx_http_healthcheck_send,
0,
0,
NULL },
/**
* What to expect in the HTTP BODY, (meaning not the headers), in a correct
* response
*/
{ ngx_string("healthcheck_expected"),
NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
ngx_http_healthcheck_expected,
0,
0,
NULL },
/**
* How big a buffer to use for the health check. Remember to include
* headers PLUS body, not just body.
*/
{ ngx_string("healthcheck_buffer"),
NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
ngx_http_healthcheck_buffer,
0,
0,
NULL },
/**
* When inside a /location block, replaced the HTTP body with backend
* health status. Use similarly to the stub_status module
*/
{ ngx_string("healthcheck_status"),