2021-02-16 07:31:30 -05:00
/*
* Server - state management functions .
*
* Copyright ( C ) 2021 HAProxy Technologies , Christopher Faulet < cfaulet @ haproxy . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*
*/
# include <errno.h>
# include <import/eb64tree.h>
# include <import/xxhash.h>
# include <haproxy/api.h>
# include <haproxy/backend.h>
# include <haproxy/cfgparse.h>
# include <haproxy/check.h>
# include <haproxy/errors.h>
# include <haproxy/global.h>
# include <haproxy/log.h>
# include <haproxy/port_range.h>
# include <haproxy/proxy.h>
# include <haproxy/resolvers.h>
# include <haproxy/server.h>
# include <haproxy/ssl_sock.h>
/* Update a server state using the parameters available in the params list.
* The caller must provide a supported version
* Grabs the server lock during operation .
*/
static void srv_state_srv_update ( struct server * srv , int version , char * * params )
{
char * p ;
struct buffer * msg ;
const char * warning ;
/* fields since version 1
* and common to all other upcoming versions
*/
enum srv_state srv_op_state ;
enum srv_admin srv_admin_state ;
unsigned srv_uweight , srv_iweight ;
unsigned long srv_last_time_change ;
short srv_check_status ;
enum chk_result srv_check_result ;
int srv_check_health ;
int srv_check_state , srv_agent_state ;
int bk_f_forced_id ;
int srv_f_forced_id ;
int fqdn_set_by_cli ;
const char * fqdn ;
const char * port_st ;
unsigned int port_svc ;
char * srvrecord ;
char * addr ;
int partial_apply = 0 ;
# ifdef USE_OPENSSL
int use_ssl ;
# endif
fqdn = NULL ;
port_svc = 0 ;
msg = alloc_trash_chunk ( ) ;
if ( ! msg )
goto end ;
HA_SPIN_LOCK ( SERVER_LOCK , & srv - > lock ) ;
/* Only version 1 supported for now, don't check it. Fields are :
* srv_addr : params [ 0 ]
* srv_op_state : params [ 1 ]
* srv_admin_state : params [ 2 ]
* srv_uweight : params [ 3 ]
* srv_iweight : params [ 4 ]
* srv_last_time_change : params [ 5 ]
* srv_check_status : params [ 6 ]
* srv_check_result : params [ 7 ]
* srv_check_health : params [ 8 ]
* srv_check_state : params [ 9 ]
* srv_agent_state : params [ 10 ]
* bk_f_forced_id : params [ 11 ]
* srv_f_forced_id : params [ 12 ]
* srv_fqdn : params [ 13 ]
* srv_port : params [ 14 ]
* srvrecord : params [ 15 ]
* srv_use_ssl : params [ 16 ]
* srv_check_port : params [ 17 ]
* srv_check_addr : params [ 18 ]
* srv_agent_addr : params [ 19 ]
* srv_agent_port : params [ 20 ]
*/
/* validating srv_op_state */
p = NULL ;
errno = 0 ;
srv_op_state = strtol ( params [ 1 ] , & p , 10 ) ;
if ( ( p = = params [ 1 ] ) | | errno = = EINVAL | | errno = = ERANGE | |
( srv_op_state ! = SRV_ST_STOPPED & &
srv_op_state ! = SRV_ST_STARTING & &
srv_op_state ! = SRV_ST_RUNNING & &
srv_op_state ! = SRV_ST_STOPPING ) ) {
chunk_appendf ( msg , " , invalid srv_op_state value '%s' " , params [ 1 ] ) ;
}
/* validating srv_admin_state */
p = NULL ;
errno = 0 ;
srv_admin_state = strtol ( params [ 2 ] , & p , 10 ) ;
fqdn_set_by_cli = ! ! ( srv_admin_state & SRV_ADMF_HMAINT ) ;
/* inherited statuses will be recomputed later.
* Also disable SRV_ADMF_HMAINT flag ( set from stats socket fqdn ) .
*/
srv_admin_state & = ~ SRV_ADMF_IDRAIN & ~ SRV_ADMF_IMAINT & ~ SRV_ADMF_HMAINT & ~ SRV_ADMF_RMAINT ;
if ( ( p = = params [ 2 ] ) | | errno = = EINVAL | | errno = = ERANGE | |
( srv_admin_state ! = 0 & &
srv_admin_state ! = SRV_ADMF_FMAINT & &
srv_admin_state ! = SRV_ADMF_CMAINT & &
srv_admin_state ! = ( SRV_ADMF_CMAINT | SRV_ADMF_FMAINT ) & &
srv_admin_state ! = ( SRV_ADMF_CMAINT | SRV_ADMF_FDRAIN ) & &
srv_admin_state ! = SRV_ADMF_FDRAIN ) ) {
chunk_appendf ( msg , " , invalid srv_admin_state value '%s' " , params [ 2 ] ) ;
}
/* validating srv_uweight */
p = NULL ;
errno = 0 ;
srv_uweight = strtol ( params [ 3 ] , & p , 10 ) ;
if ( ( p = = params [ 3 ] ) | | errno = = EINVAL | | errno = = ERANGE | | ( srv_uweight > SRV_UWGHT_MAX ) )
chunk_appendf ( msg , " , invalid srv_uweight value '%s' " , params [ 3 ] ) ;
/* validating srv_iweight */
p = NULL ;
errno = 0 ;
srv_iweight = strtol ( params [ 4 ] , & p , 10 ) ;
if ( ( p = = params [ 4 ] ) | | errno = = EINVAL | | errno = = ERANGE | | ( srv_iweight > SRV_UWGHT_MAX ) )
chunk_appendf ( msg , " , invalid srv_iweight value '%s' " , params [ 4 ] ) ;
/* validating srv_last_time_change */
p = NULL ;
errno = 0 ;
srv_last_time_change = strtol ( params [ 5 ] , & p , 10 ) ;
if ( ( p = = params [ 5 ] ) | | errno = = EINVAL | | errno = = ERANGE )
chunk_appendf ( msg , " , invalid srv_last_time_change value '%s' " , params [ 5 ] ) ;
/* validating srv_check_status */
p = NULL ;
errno = 0 ;
srv_check_status = strtol ( params [ 6 ] , & p , 10 ) ;
if ( p = = params [ 6 ] | | errno = = EINVAL | | errno = = ERANGE | |
( srv_check_status > = HCHK_STATUS_SIZE ) )
chunk_appendf ( msg , " , invalid srv_check_status value '%s' " , params [ 6 ] ) ;
/* validating srv_check_result */
p = NULL ;
errno = 0 ;
srv_check_result = strtol ( params [ 7 ] , & p , 10 ) ;
if ( ( p = = params [ 7 ] ) | | errno = = EINVAL | | errno = = ERANGE | |
( srv_check_result ! = CHK_RES_UNKNOWN & &
srv_check_result ! = CHK_RES_NEUTRAL & &
srv_check_result ! = CHK_RES_FAILED & &
srv_check_result ! = CHK_RES_PASSED & &
srv_check_result ! = CHK_RES_CONDPASS ) ) {
chunk_appendf ( msg , " , invalid srv_check_result value '%s' " , params [ 7 ] ) ;
}
/* validating srv_check_health */
p = NULL ;
errno = 0 ;
srv_check_health = strtol ( params [ 8 ] , & p , 10 ) ;
if ( p = = params [ 8 ] | | errno = = EINVAL | | errno = = ERANGE )
chunk_appendf ( msg , " , invalid srv_check_health value '%s' " , params [ 8 ] ) ;
/* validating srv_check_state */
p = NULL ;
errno = 0 ;
srv_check_state = strtol ( params [ 9 ] , & p , 10 ) ;
if ( p = = params [ 9 ] | | errno = = EINVAL | | errno = = ERANGE | |
( srv_check_state & ~ ( CHK_ST_INPROGRESS | CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_PAUSED | CHK_ST_AGENT ) ) )
chunk_appendf ( msg , " , invalid srv_check_state value '%s' " , params [ 9 ] ) ;
/* validating srv_agent_state */
p = NULL ;
errno = 0 ;
srv_agent_state = strtol ( params [ 10 ] , & p , 10 ) ;
if ( p = = params [ 10 ] | | errno = = EINVAL | | errno = = ERANGE | |
( srv_agent_state & ~ ( CHK_ST_INPROGRESS | CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_PAUSED | CHK_ST_AGENT ) ) )
chunk_appendf ( msg , " , invalid srv_agent_state value '%s' " , params [ 10 ] ) ;
/* validating bk_f_forced_id */
p = NULL ;
errno = 0 ;
bk_f_forced_id = strtol ( params [ 11 ] , & p , 10 ) ;
if ( p = = params [ 11 ] | | errno = = EINVAL | | errno = = ERANGE | | ! ( ( bk_f_forced_id = = 0 ) | | ( bk_f_forced_id = = 1 ) ) )
chunk_appendf ( msg , " , invalid bk_f_forced_id value '%s' " , params [ 11 ] ) ;
/* validating srv_f_forced_id */
p = NULL ;
errno = 0 ;
srv_f_forced_id = strtol ( params [ 12 ] , & p , 10 ) ;
if ( p = = params [ 12 ] | | errno = = EINVAL | | errno = = ERANGE | | ! ( ( srv_f_forced_id = = 0 ) | | ( srv_f_forced_id = = 1 ) ) )
chunk_appendf ( msg , " , invalid srv_f_forced_id value '%s' " , params [ 12 ] ) ;
/* validating srv_fqdn */
fqdn = params [ 13 ] ;
if ( fqdn & & * fqdn = = ' - ' )
fqdn = NULL ;
if ( fqdn & & ( strlen ( fqdn ) > DNS_MAX_NAME_SIZE | | invalid_domainchar ( fqdn ) ) ) {
chunk_appendf ( msg , " , invalid srv_fqdn value '%s' " , params [ 13 ] ) ;
fqdn = NULL ;
}
port_st = params [ 14 ] ;
if ( port_st ) {
port_svc = strl2uic ( port_st , strlen ( port_st ) ) ;
if ( port_svc > USHRT_MAX ) {
chunk_appendf ( msg , " , invalid srv_port value '%s' " , port_st ) ;
port_st = NULL ;
}
}
/* SRV record
* NOTE : in HAProxy , SRV records must start with an underscore ' _ '
*/
srvrecord = params [ 15 ] ;
if ( srvrecord & & * srvrecord ! = ' _ ' )
srvrecord = NULL ;
/* don't apply anything if one error has been detected */
if ( msg - > data )
goto out ;
partial_apply = 1 ;
/* recover operational state and apply it to this server
* and all servers tracking this one */
srv - > check . health = srv_check_health ;
switch ( srv_op_state ) {
case SRV_ST_STOPPED :
srv - > check . health = 0 ;
srv_set_stopped ( srv , " changed from server-state after a reload " , NULL ) ;
break ;
case SRV_ST_STARTING :
/* If rise == 1 there is no STARTING state, let's switch to
* RUNNING
*/
if ( srv - > check . rise = = 1 ) {
srv - > check . health = srv - > check . rise + srv - > check . fall - 1 ;
srv_set_running ( srv , " " , NULL ) ;
break ;
}
if ( srv - > check . health < 1 | | srv - > check . health > = srv - > check . rise )
srv - > check . health = srv - > check . rise - 1 ;
srv - > next_state = srv_op_state ;
break ;
case SRV_ST_STOPPING :
/* If fall == 1 there is no STOPPING state, let's switch to
* STOPPED
*/
if ( srv - > check . fall = = 1 ) {
srv - > check . health = 0 ;
srv_set_stopped ( srv , " changed from server-state after a reload " , NULL ) ;
break ;
}
if ( srv - > check . health < srv - > check . rise | |
srv - > check . health > srv - > check . rise + srv - > check . fall - 2 )
srv - > check . health = srv - > check . rise ;
srv_set_stopping ( srv , " changed from server-state after a reload " , NULL ) ;
break ;
case SRV_ST_RUNNING :
srv - > check . health = srv - > check . rise + srv - > check . fall - 1 ;
srv_set_running ( srv , " " , NULL ) ;
break ;
}
/* When applying server state, the following rules apply:
* - in case of a configuration change , we apply the setting from the new
* configuration , regardless of old running state
* - if no configuration change , we apply old running state only if old running
* state is different from new configuration state
*/
/* configuration has changed */
if ( ( srv_admin_state & SRV_ADMF_CMAINT ) ! = ( srv - > next_admin & SRV_ADMF_CMAINT ) ) {
if ( srv - > next_admin & SRV_ADMF_CMAINT )
srv_adm_set_maint ( srv ) ;
else
srv_adm_set_ready ( srv ) ;
}
/* configuration is the same, let's compate old running state and new conf state */
else {
if ( srv_admin_state & SRV_ADMF_FMAINT & & ! ( srv - > next_admin & SRV_ADMF_CMAINT ) )
srv_adm_set_maint ( srv ) ;
else if ( ! ( srv_admin_state & SRV_ADMF_FMAINT ) & & ( srv - > next_admin & SRV_ADMF_CMAINT ) )
srv_adm_set_ready ( srv ) ;
}
/* apply drain mode if server is currently enabled */
if ( ! ( srv - > next_admin & SRV_ADMF_FMAINT ) & & ( srv_admin_state & SRV_ADMF_FDRAIN ) ) {
/* The SRV_ADMF_FDRAIN flag is inherited when srv->iweight is 0
* ( srv - > iweight is the weight set up in configuration ) .
* There are two possible reasons for FDRAIN to have been present :
* - previous config weight was zero
* - " set server b/s drain " was sent to the CLI
*
* In the first case , we simply want to drop this drain state
* if the new weight is not zero anymore , meaning the administrator
* has intentionally turned the weight back to a positive value to
* enable the server again after an operation . In the second case ,
* the drain state was forced on the CLI regardless of the config ' s
* weight so we don ' t want a change to the config weight to lose this
* status . What this means is :
* - if previous weight was 0 and new one is > 0 , drop the DRAIN state .
* - if the previous weight was > 0 , keep it .
*/
if ( srv_iweight > 0 | | srv - > iweight = = 0 )
srv_adm_set_drain ( srv ) ;
}
srv - > last_change = date . tv_sec - srv_last_time_change ;
srv - > check . status = srv_check_status ;
srv - > check . result = srv_check_result ;
/* Only case we want to apply is removing ENABLED flag which could have been
* done by the " disable health " command over the stats socket
*/
if ( ( srv - > check . state & CHK_ST_CONFIGURED ) & &
( srv_check_state & CHK_ST_CONFIGURED ) & &
! ( srv_check_state & CHK_ST_ENABLED ) )
srv - > check . state & = ~ CHK_ST_ENABLED ;
/* Only case we want to apply is removing ENABLED flag which could have been
* done by the " disable agent " command over the stats socket
*/
if ( ( srv - > agent . state & CHK_ST_CONFIGURED ) & &
( srv_agent_state & CHK_ST_CONFIGURED ) & &
! ( srv_agent_state & CHK_ST_ENABLED ) )
srv - > agent . state & = ~ CHK_ST_ENABLED ;
/* We want to apply the previous 'running' weight (srv_uweight) only if there
* was no change in the configuration : both previous and new iweight are equals
*
* It means that a configuration file change has precedence over a unix socket change
* for server ' s weight
*
* by default , HAProxy applies the following weight when parsing the configuration
* srv - > uweight = srv - > iweight
*/
if ( srv_iweight = = srv - > iweight ) {
srv - > uweight = srv_uweight ;
}
server_recalc_eweight ( srv , 1 ) ;
/* load server IP address */
if ( strcmp ( params [ 0 ] , " - " ) ! = 0 )
srv - > lastaddr = strdup ( params [ 0 ] ) ;
if ( fqdn & & srv - > hostname ) {
if ( strcmp ( srv - > hostname , fqdn ) = = 0 ) {
/* Here we reset the 'set from stats socket FQDN' flag
* to support such transitions :
* Let ' s say initial FQDN value is foo1 ( in configuration file ) .
* - FQDN changed from stats socket , from foo1 to foo2 value ,
* - FQDN changed again from file configuration ( with the same previous value
set from stats socket , from foo1 to foo2 value ) ,
* - reload for any other reason than a FQDN modification ,
* the configuration file FQDN matches the fqdn server state file value .
* So we must reset the ' set from stats socket FQDN ' flag to be consistent with
* any further FQDN modification .
*/
srv - > next_admin & = ~ SRV_ADMF_HMAINT ;
}
else {
/* If the FDQN has been changed from stats socket,
* apply fqdn state file value ( which is the value set
* from stats socket ) .
* Also ensure the runtime resolver will process this resolution .
*/
if ( fqdn_set_by_cli ) {
srv_set_fqdn ( srv , fqdn , 0 ) ;
srv - > flags & = ~ SRV_F_NO_RESOLUTION ;
srv - > next_admin | = SRV_ADMF_HMAINT ;
}
}
}
/* If all the conditions below are validated, this means
* we ' re evaluating a server managed by SRV resolution
*/
else if ( fqdn & & ! srv - > hostname & & srvrecord ) {
int res ;
/* we can't apply previous state if SRV record has changed */
if ( srv - > srvrq & & strcmp ( srv - > srvrq - > name , srvrecord ) ! = 0 ) {
chunk_appendf ( msg , " , SRV record mismatch between configuration ('%s') and state file ('%s) for server '%s'. Previous state not applied " , srv - > srvrq - > name , srvrecord , srv - > id ) ;
goto out ;
}
/* create or find a SRV resolution for this srv record */
if ( srv - > srvrq = = NULL & & ( srv - > srvrq = find_srvrq_by_name ( srvrecord , srv - > proxy ) ) = = NULL )
srv - > srvrq = new_resolv_srvrq ( srv , srvrecord ) ;
if ( srv - > srvrq = = NULL ) {
chunk_appendf ( msg , " , can't create or find SRV resolution '%s' for server '%s' " , srvrecord , srv - > id ) ;
goto out ;
}
/* prepare DNS resolution for this server */
res = srv_prepare_for_resolution ( srv , fqdn ) ;
if ( res = = - 1 ) {
chunk_appendf ( msg , " , can't allocate memory for DNS resolution for server '%s' " , srv - > id ) ;
goto out ;
}
/* Unset SRV_F_MAPPORTS for SRV records.
* SRV_F_MAPPORTS is unfortunately set by parse_server ( )
* because no ports are provided in the configuration file .
* This is because HAProxy will use the port found into the SRV record .
*/
srv - > flags & = ~ SRV_F_MAPPORTS ;
}
if ( port_st )
srv - > svc_port = port_svc ;
if ( params [ 16 ] ) {
# ifdef USE_OPENSSL
use_ssl = strtol ( params [ 16 ] , & p , 10 ) ;
/* configure ssl if connection has been initiated at startup */
if ( srv - > ssl_ctx . ctx ! = NULL )
ssl_sock_set_srv ( srv , use_ssl ) ;
# endif
}
port_st = NULL ;
if ( params [ 17 ] & & strcmp ( params [ 17 ] , " 0 " ) ! = 0 )
port_st = params [ 17 ] ;
addr = NULL ;
if ( params [ 18 ] & & strcmp ( params [ 18 ] , " - " ) ! = 0 )
addr = params [ 18 ] ;
if ( addr | | port_st ) {
warning = srv_update_check_addr_port ( srv , addr , port_st ) ;
if ( warning ) {
chunk_appendf ( msg , " , %s " , warning ) ;
goto out ;
}
}
port_st = NULL ;
if ( params [ 20 ] & & strcmp ( params [ 20 ] , " 0 " ) ! = 0 )
port_st = params [ 20 ] ;
addr = NULL ;
if ( params [ 19 ] & & strcmp ( params [ 19 ] , " - " ) ! = 0 )
addr = params [ 19 ] ;
if ( addr | | port_st ) {
warning = srv_update_agent_addr_port ( srv , addr , port_st ) ;
if ( warning ) {
chunk_appendf ( msg , " , %s " , warning ) ;
goto out ;
}
}
out :
HA_SPIN_UNLOCK ( SERVER_LOCK , & srv - > lock ) ;
if ( msg - > data ) {
if ( partial_apply = = 1 )
ha_warning ( " server-state partially applied for server '%s/%s'%s \n " ,
srv - > proxy - > id , srv - > id , msg - > area ) ;
else
ha_warning ( " server-state application failed for server '%s/%s'%s \n " ,
srv - > proxy - > id , srv - > id , msg - > area ) ;
}
end :
free_trash_chunk ( msg ) ;
}
/*
* Loop on the proxy ' s servers and try to load its state from < st_tree > using
* srv_state_srv_update ( ) . The proxy name and the server name are concatenated
* to form the key . If found the entry is removed from the tree .
*/
static void srv_state_px_update ( const struct proxy * px , int vsn , struct eb_root * st_tree )
{
struct server_state_line * st_line ;
struct eb64_node * node ;
struct server * srv ;
unsigned long key ;
for ( srv = px - > srv ; srv ; srv = srv - > next ) {
chunk_printf ( & trash , " %s %s " , px - > id , srv - > id ) ;
key = XXH3 ( trash . area , trash . data , 0 ) ;
node = eb64_lookup ( st_tree , key ) ;
if ( ! node )
continue ; /* next server */
st_line = eb64_entry ( node , typeof ( * st_line ) , node ) ;
srv_state_srv_update ( srv , vsn , st_line - > params + 4 ) ;
/* the node may be released now */
eb64_delete ( node ) ;
free ( st_line - > line ) ;
free ( st_line ) ;
}
}
/*
* read next line from file < f > and return the server state version if one found .
* If no version is found , then 0 is returned
* Note that this should be the first read on < f >
*/
static int srv_state_get_version ( FILE * f ) {
char mybuf [ SRV_STATE_LINE_MAXLEN ] ;
char * endptr ;
long int vsn ;
/* first character of first line of the file must contain the version of the export */
if ( fgets ( mybuf , SRV_STATE_LINE_MAXLEN , f ) = = NULL )
return 0 ;
vsn = strtol ( mybuf , & endptr , 10 ) ;
if ( endptr = = mybuf | | * endptr ! = ' \n ' ) {
/* Empty or truncated line */
return 0 ;
}
if ( vsn < SRV_STATE_FILE_VERSION_MIN | | vsn > SRV_STATE_FILE_VERSION_MAX ) {
/* Wrong version number */
return 0 ;
}
return vsn ;
}
/*
* parses server state line stored in < buf > and supposedly in version < version > .
* Set < params > accordingly on success . It returns 1 on success , 0 if the line
* must be ignored and - 1 on error .
* The caller must provide a supported version
*/
static int srv_state_parse_line ( char * buf , const int version , char * * params )
{
int buflen , arg , ret ;
char * cur ;
buflen = strlen ( buf ) ;
cur = buf ;
ret = 1 ; /* be optimistic and pretend a success */
/* we need at least one character and a non-truncated line */
if ( buflen = = 0 | | buf [ buflen - 1 ] ! = ' \n ' ) {
ret = - 1 ;
goto out ;
}
/* skip blank characters at the beginning of the line */
while ( isblank ( ( unsigned char ) * cur ) )
+ + cur ;
/* ignore empty or commented lines */
if ( ! * cur | | * cur = = ' \n ' | | * cur = = ' # ' ) {
ret = 0 ;
goto out ;
}
/* Removes trailing '\n' to ease parsing */
buf [ buflen - 1 ] = ' \0 ' ;
/* we're now ready to move the line into <params> */
memset ( params , 0 , SRV_STATE_FILE_MAX_FIELDS * sizeof ( * params ) ) ;
arg = 0 ;
while ( * cur ) {
/* first of all, stop if there are too many fields */
if ( arg > = SRV_STATE_FILE_MAX_FIELDS )
break ;
/* then skip leading spaces */
while ( * cur & & isblank ( ( unsigned char ) * cur ) ) {
+ + cur ;
if ( ! * cur )
break ;
}
/*
* idx :
* be_id : params [ 0 ]
* be_name : params [ 1 ]
* srv_id : params [ 2 ]
* srv_name : params [ 3 ]
* v1
* srv_addr : params [ 4 ]
* srv_op_state : params [ 5 ]
* srv_admin_state : params [ 6 ]
* srv_uweight : params [ 7 ]
* srv_iweight : params [ 8 ]
* srv_last_time_change : params [ 9 ]
* srv_check_status : params [ 10 ]
* srv_check_result : params [ 11 ]
* srv_check_health : params [ 12 ]
* srv_check_state : params [ 13 ]
* srv_agent_state : params [ 14 ]
* bk_f_forced_id : params [ 15 ]
* srv_f_forced_id : params [ 16 ]
* srv_fqdn : params [ 17 ]
* srv_port : params [ 18 ]
* srvrecord : params [ 19 ]
*
* srv_use_ssl : params [ 20 ] ( optional field )
* srv_check_port : params [ 21 ] ( optional field )
* srv_check_addr : params [ 22 ] ( optional field )
* srv_agent_addr : params [ 23 ] ( optional field )
* srv_agent_port : params [ 24 ] ( optional field )
*
*/
params [ arg + + ] = cur ;
/* look for the end of the current field */
while ( * cur & & ! isblank ( ( unsigned char ) * cur ) ) {
+ + cur ;
if ( ! * cur )
break ;
}
/* otherwise, cut the field and move to the next one */
* cur + + = ' \0 ' ;
}
/* if the number of fields does not match the version, then return an error */
if ( version = = 1 & &
( arg < SRV_STATE_FILE_MIN_FIELDS_VERSION_1 | |
arg > SRV_STATE_FILE_MAX_FIELDS_VERSION_1 ) )
ret = - 1 ;
out :
return ret ;
}
/*
* parses a server state line using srv_state_parse_line ( ) and store the result
* in < st_tree > . If an error occurred during the parsing , the line is
* ignored . if < px > is defined , it is used to check the backend id / name against
* the parsed params and to compute the key of the line .
*/
static int srv_state_parse_and_store_line ( char * line , int vsn , struct eb_root * st_tree ,
struct proxy * px )
{
struct server_state_line * st_line ;
int ret = 0 ;
/* store line in tree and duplicate the line */
st_line = calloc ( 1 , sizeof ( * st_line ) ) ;
if ( st_line = = NULL )
goto skip_line ;
st_line - > line = strdup ( line ) ;
if ( st_line - > line = = NULL )
goto skip_line ;
ret = srv_state_parse_line ( st_line - > line , vsn , st_line - > params ) ;
if ( ret < = 0 )
goto skip_line ;
/* Check backend name against params if <px> is defined */
if ( px ) {
int check_id = ( atoi ( st_line - > params [ 0 ] ) = = px - > uuid ) ;
int check_name = ( strcmp ( px - > id , st_line - > params [ 1 ] ) = = 0 ) ;
int bk_f_forced_id = ( atoi ( st_line - > params [ 15 ] ) & PR_O_FORCED_ID ) ;
if ( ! check_id & & ! check_name ) {
/* backend does not match at all: skip the line */
goto skip_line ;
}
else if ( ! check_id ) {
/* Id mismatch: warn but continue */
ha_warning ( " Proxy '%s': backend ID mismatch: from server state file: '%s', from running config '%d' \n " ,
px - > id , st_line - > params [ 0 ] , px - > uuid ) ;
send_log ( px , LOG_NOTICE , " backend ID mismatch: from server state file: '%s', from running config '%d' \n " ,
st_line - > params [ 0 ] , px - > uuid ) ;
}
else if ( ! check_name ) {
/* Name mismatch: warn and skip the line, except if the backend id was forced
* in the previous configuration */
ha_warning ( " Proxy '%s': backend name mismatch: from server state file: '%s', from running config '%s' \n " ,
px - > id , st_line - > params [ 1 ] , px - > id ) ;
send_log ( px , LOG_NOTICE , " backend name mismatch: from server state file: '%s', from running config '%s' \n " ,
st_line - > params [ 1 ] , px - > id ) ;
if ( ! bk_f_forced_id )
goto skip_line ;
}
}
/*
* The key : " be_name srv_name "
* if < px > is defined : be_name = = px - > id
* otherwise : be_name = = params [ 1 ]
*/
chunk_printf ( & trash , " %s %s " , ( px ? px - > id : st_line - > params [ 1 ] ) , st_line - > params [ 3 ] ) ;
st_line - > node . key = XXH3 ( trash . area , trash . data , 0 ) ;
if ( eb64_insert ( st_tree , & st_line - > node ) ! = & st_line - > node ) {
/* this is a duplicate key, probably a hand-crafted file, drop it! */
goto skip_line ;
}
return ret ;
skip_line :
/* free up memory in case of error during the processing of the line */
if ( st_line ) {
free ( st_line - > line ) ;
free ( st_line ) ;
}
return ret ;
}
/* Helper function to get the server-state file path.
* If < filename > starts with a ' / ' , it is considered as an absolute path . In
* this case or if < global . server_state_base > is not set , < filename > only is
* considered . Otherwise , the < global . server_state_base > is concatenated to
* < filename > to produce the file path and copied to < dst_path > . in both cases ,
* the result must not exceeds < maxpathlen > .
*
* The len is returned on success or - 1 if the path is too long . On error , the
* caller must not rely on < dst_path > .
*/
static inline int srv_state_get_filepath ( char * dst_path , int maxpathlen , const char * filename )
{
char * sep = ( global . server_state_base [ strlen ( global . server_state_base ) - 1 ] ! = ' / ' ? " / " : " " ) ;
int len = 0 ;
/* create the globalfilepath variable */
if ( * filename = = ' / ' | | ! global . server_state_base ) {
/* absolute path or no base directory provided */
len = strlen ( filename ) ;
if ( len < maxpathlen )
strcpy ( dst_path , global . server_state_file ) ;
}
else {
/* concat base directory and global server-state file */
len = snprintf ( dst_path , maxpathlen , " %s%s%s " , global . server_state_base , sep , filename ) ;
}
return ( len < maxpathlen ? len : - 1 ) ;
}
/* This function parses all the proxies and only take care of the backends (since we're looking for server)
* For each proxy , it does the following :
* - opens its server state file ( either one or local one )
* - read whole file , line by line
* - analyse each line to check if it matches our current backend :
* - backend name matches
* - backend id matches if id is forced and name doesn ' t match
* - if the server pointed by the line is found , then state is applied
*
* If the running backend uuid or id differs from the state file , then HAProxy reports
* a warning .
*
* Grabs the server ' s lock via srv_state_srv_update ( ) .
*/
void apply_server_state ( void )
{
/* tree where global state_file is loaded */
struct eb_root global_state_tree = EB_ROOT_UNIQUE ;
struct proxy * curproxy ;
struct server_state_line * st_line ;
struct eb64_node * node , * next_node ;
FILE * f ;
char mybuf [ SRV_STATE_LINE_MAXLEN ] ;
char file [ MAXPATHLEN ] ;
int local_vsn , global_vsn , len , linenum ;
global_vsn = 0 ; /* no global file */
if ( ! global . server_state_file )
goto no_globalfile ;
len = srv_state_get_filepath ( file , MAXPATHLEN , global . server_state_file ) ;
if ( len = = - 1 ) {
ha_warning ( " config: Can't load global server state file: file too long. \n " ) ;
goto no_globalfile ;
}
/* Load global server state in a tree */
errno = 0 ;
f = fopen ( file , " r " ) ;
if ( ! f ) {
ha_warning ( " config: Can't open global server state file '%s': %s \n " , file , strerror ( errno ) ) ;
goto no_globalfile ;
}
global_vsn = srv_state_get_version ( f ) ;
if ( global_vsn = = 0 ) {
ha_warning ( " config: Can't get version of the global server state file '%s'. \n " ,
file ) ;
goto close_globalfile ;
}
for ( linenum = 1 ; fgets ( mybuf , SRV_STATE_LINE_MAXLEN , f ) ; linenum + + ) {
int ret ;
ret = srv_state_parse_and_store_line ( mybuf , global_vsn , & global_state_tree , NULL ) ;
if ( ret = = - 1 ) {
ha_warning ( " config: corrupted global server state file '%s' at line %d. \n " ,
file , linenum ) ;
global_vsn = 0 ;
break ;
}
}
close_globalfile :
fclose ( f ) ;
no_globalfile :
/* parse all proxies and load states form tree (global file) or from local file */
for ( curproxy = proxies_list ; curproxy ! = NULL ; curproxy = curproxy - > next ) {
struct eb_root local_state_tree = EB_ROOT_UNIQUE ;
/* servers are only in backends */
2021-02-16 08:36:06 -05:00
if ( ! ( curproxy - > cap & PR_CAP_BE ) | | ! curproxy - > srv )
2021-02-16 07:31:30 -05:00
continue ; /* next proxy */
/* No server-state file for this proxy */
if ( curproxy - > load_server_state_from_file = = PR_SRV_STATE_FILE_NONE )
continue ; /* next proxy */
if ( curproxy - > load_server_state_from_file = = PR_SRV_STATE_FILE_GLOBAL ) {
/* when global file is used, we get data from the tree
* Note that in such case we don ' t check backend name neither uuid .
* Backend name can ' t be wrong since it ' s used as a key to retrieve the server state
* line from the tree .
*/
if ( global_vsn )
srv_state_px_update ( curproxy , global_vsn , & global_state_tree ) ;
continue ; /* next proxy */
}
/*
* Here we load a local server state - file
*/
/* create file variable */
len = srv_state_get_filepath ( file , MAXPATHLEN , curproxy - > server_state_file_name ) ;
if ( len = = - 1 ) {
ha_warning ( " Proxy '%s': Can't load local server state file: file too long. \n " , curproxy - > id ) ;
continue ; /* next proxy */
}
/* Load local server state in a tree */
errno = 0 ;
f = fopen ( file , " r " ) ;
if ( ! f ) {
ha_warning ( " Proxy '%s': Can't open server state file '%s': %s. \n " ,
curproxy - > id , file , strerror ( errno ) ) ;
continue ; /* next proxy */
}
/* first character of first line of the file must contain the version of the export */
local_vsn = srv_state_get_version ( f ) ;
if ( local_vsn = = 0 ) {
ha_warning ( " Proxy '%s': Can't get version of the server state file '%s'. \n " ,
curproxy - > id , file ) ;
goto close_localfile ;
}
/* First, parse lines of the local server-state file and store them in a eb-tree */
for ( linenum = 1 ; fgets ( mybuf , SRV_STATE_LINE_MAXLEN , f ) ; linenum + + ) {
int ret ;
ret = srv_state_parse_and_store_line ( mybuf , local_vsn , & local_state_tree , curproxy ) ;
if ( ret = = - 1 ) {
ha_warning ( " Proxy '%s': corrupted server state file '%s' at line %d. \n " ,
curproxy - > id , file , linenum ) ;
local_vsn = 0 ;
break ;
}
}
if ( local_vsn )
srv_state_px_update ( curproxy , local_vsn , & local_state_tree ) ;
/* Remove unsused server-state lines */
node = eb64_first ( & local_state_tree ) ;
while ( node ) {
st_line = eb64_entry ( node , typeof ( * st_line ) , node ) ;
next_node = eb64_next ( node ) ;
eb64_delete ( node ) ;
if ( local_vsn ) {
/* if no server found, then warn */
ha_warning ( " Proxy '%s': can't find server '%s' in backend '%s' \n " ,
curproxy - > id , st_line - > params [ 3 ] , curproxy - > id ) ;
send_log ( curproxy , LOG_NOTICE , " can't find server '%s' in backend '%s' \n " ,
st_line - > params [ 3 ] , curproxy - > id ) ;
}
free ( st_line - > line ) ;
free ( st_line ) ;
node = next_node ;
}
close_localfile :
fclose ( f ) ;
}
node = eb64_first ( & global_state_tree ) ;
while ( node ) {
st_line = eb64_entry ( node , typeof ( * st_line ) , node ) ;
next_node = eb64_next ( node ) ;
eb64_delete ( node ) ;
free ( st_line - > line ) ;
free ( st_line ) ;
node = next_node ;
}
}