Commit a92cf57d authored by Ondřej Zajíček's avatar Ondřej Zajíček

Implements undo command and optional timeout for configuration

Several new configure command variants:

configure undo - undo last reconfiguration
configure timeout - configure with scheduled undo if not confirmed in timeout
configure confirm - confirm last configuration
configure check - just parse and validate config file
parent 80a9cadc
......@@ -21,9 +21,12 @@
* There can exist up to four different configurations at one time: an active
* one (pointed to by @config), configuration we are just switching from
* (@old_config), one queued for the next reconfiguration (@future_config;
* if it's non-%NULL and the user wants to reconfigure once again, we just
* if there is one and the user wants to reconfigure once again, we just
* free the previous queued config and replace it with the new one) and
* finally a config being parsed (@new_config).
* finally a config being parsed (@new_config). The stored @old_config
* is also used for undo reconfiguration, which works in a similar way.
* Reconfiguration could also have timeout (using @config_timer) and undo
* is automatically called if the new configuration is not confirmed later.
*
* Loading of new configuration is very simple: just call config_alloc()
* to get a new &config structure, then use config_parse() to parse a
......@@ -55,10 +58,23 @@
static jmp_buf conf_jmpbuf;
struct config *config, *new_config, *old_config, *future_config;
static event *config_event;
int shutting_down, future_type;
bird_clock_t boot_time;
struct config *config, *new_config;
static struct config *old_config; /* Old configuration */
static struct config *future_config; /* New config held here if recon requested during recon */
static int old_cftype; /* Type of transition old_config -> config (RECONFIG_SOFT/HARD) */
static int future_cftype; /* Type of scheduled transition, may also be RECONFIG_UNDO */
/* Note that when future_cftype is RECONFIG_UNDO, then future_config is NULL,
therefore proper check for future scheduled config checks future_cftype */
static event *config_event; /* Event for finalizing reconfiguration */
static timer *config_timer; /* Timer for scheduled configuration rollback */
/* These are public just for cmd_show_status(), should not be accessed elsewhere */
int shutting_down; /* Shutdown requested, do not accept new config changes */
int configuring; /* Reconfiguration is running */
int undo_available; /* Undo was not requested from last reconfiguration */
/* Note that both shutting_down and undo_available are related to requests, not processing */
/**
* config_alloc - allocate a new configuration
......@@ -82,8 +98,6 @@ config_alloc(byte *name)
c->load_time = now;
c->tf_base.fmt1 = c->tf_log.fmt1 = "%d-%m-%Y %T";
if (!boot_time)
boot_time = now;
return c;
}
......@@ -154,7 +168,8 @@ cli_parse(struct config *c)
void
config_free(struct config *c)
{
rfree(c->pool);
if (c)
rfree(c->pool);
}
void
......@@ -170,10 +185,7 @@ config_del_obstacle(struct config *c)
DBG("+++ deleting obstacle %d\n", c->obstacle_count);
c->obstacle_count--;
if (!c->obstacle_count)
{
ASSERT(config_event);
ev_schedule(config_event);
}
ev_schedule(config_event);
}
static int
......@@ -197,16 +209,31 @@ global_commit(struct config *new, struct config *old)
static int
config_do_commit(struct config *c, int type)
{
int force_restart, nobs;
if (type == RECONFIG_UNDO)
{
c = old_config;
type = old_cftype;
}
else
config_free(old_config);
DBG("do_commit\n");
old_config = config;
config = new_config = c;
old_cftype = type;
config = c;
configuring = 1;
if (old_config && !config->shutdown)
log(L_INFO "Reconfiguring");
/* This should not be necessary, but it seems there are some
functions that access new_config instead of config */
new_config = config;
if (old_config)
old_config->obstacle_count++;
DBG("sysdep_commit\n");
force_restart = sysdep_commit(c, old_config);
int force_restart = sysdep_commit(c, old_config);
DBG("global_commit\n");
force_restart |= global_commit(c, old_config);
DBG("rt_commit\n");
......@@ -214,38 +241,38 @@ config_do_commit(struct config *c, int type)
roa_commit(c, old_config);
DBG("protos_commit\n");
protos_commit(c, old_config, force_restart, type);
new_config = NULL; /* Just to be sure nobody uses that now */
/* Just to be sure nobody uses that now */
new_config = NULL;
int obs = 0;
if (old_config)
nobs = --old_config->obstacle_count;
else
nobs = 0;
DBG("do_commit finished with %d obstacles remaining\n", nobs);
return !nobs;
obs = --old_config->obstacle_count;
DBG("do_commit finished with %d obstacles remaining\n", obs);
return !obs;
}
static void
config_done(void *unused UNUSED)
{
struct config *c;
if (config->shutdown)
sysdep_shutdown_done();
configuring = 0;
if (old_config)
log(L_INFO "Reconfigured");
DBG("config_done\n");
for(;;)
if (future_cftype)
{
if (config->shutdown)
sysdep_shutdown_done();
log(L_INFO "Reconfigured");
if (old_config)
{
config_free(old_config);
old_config = NULL;
}
if (!future_config)
break;
c = future_config;
int type = future_cftype;
struct config *conf = future_config;
future_cftype = RECONFIG_NONE;
future_config = NULL;
log(L_INFO "Reconfiguring to queued configuration");
if (!config_do_commit(c, future_type))
break;
if (config_do_commit(conf, type))
config_done(NULL);
}
}
......@@ -253,6 +280,7 @@ config_done(void *unused UNUSED)
* config_commit - commit a configuration
* @c: new configuration
* @type: type of reconfiguration (RECONFIG_SOFT or RECONFIG_HARD)
* @timeout: timeout for undo (or 0 for no timeout)
*
* When a configuration is parsed and prepared for use, the
* config_commit() function starts the process of reconfiguration.
......@@ -265,6 +293,10 @@ config_done(void *unused UNUSED)
* using config_del_obstacle(), the old configuration is freed and
* everything runs according to the new one.
*
* When @timeout is nonzero, the undo timer is activated with given
* timeout. The timer is deactivated when config_commit(),
* config_confirm() or config_undo() is called.
*
* Result: %CONF_DONE if the configuration has been accepted immediately,
* %CONF_PROGRESS if it will take some time to switch to it, %CONF_QUEUED
* if it's been queued due to another reconfiguration being in progress now
......@@ -272,49 +304,147 @@ config_done(void *unused UNUSED)
* are accepted.
*/
int
config_commit(struct config *c, int type)
config_commit(struct config *c, int type, int timeout)
{
if (!config) /* First-time configuration */
if (shutting_down)
{
config_do_commit(c, RECONFIG_HARD);
return CONF_DONE;
config_free(c);
return CONF_SHUTDOWN;
}
if (old_config) /* Reconfiguration already in progress */
undo_available = 1;
if (timeout > 0)
tm_start(config_timer, timeout);
else
tm_stop(config_timer);
if (configuring)
{
if (shutting_down == 2)
{
log(L_INFO "New configuration discarded due to shutdown");
config_free(c);
return CONF_SHUTDOWN;
}
if (future_config)
if (future_cftype)
{
log(L_INFO "Queueing new configuration, ignoring the one already queued");
config_free(future_config);
}
else
log(L_INFO "Queued new configuration");
log(L_INFO "Queueing new configuration");
future_cftype = type;
future_config = c;
future_type = type;
return CONF_QUEUED;
}
if (!shutting_down)
log(L_INFO "Reconfiguring");
if (config_do_commit(c, type))
{
config_done(NULL);
return CONF_DONE;
}
if (!config_event)
return CONF_PROGRESS;
}
/**
* config_confirm - confirm a commited configuration
*
* When the undo timer is activated by config_commit() with nonzero timeout,
* this function can be used to deactivate it and therefore confirm
* the current configuration.
*
* Result: %CONF_CONFIRM when the current configuration is confirmed,
* %CONF_NONE when there is nothing to confirm (i.e. undo timer is not active).
*/
int
config_confirm(void)
{
if (config_timer->expires == 0)
return CONF_NOTHING;
tm_stop(config_timer);
return CONF_CONFIRM;
}
/**
* config_undo - undo a configuration
*
* Function config_undo() can be used to change the current
* configuration back to stored %old_config. If no reconfiguration is
* running, this stored configuration is commited in the same way as a
* new configuration in config_commit(). If there is already a
* reconfiguration in progress and no next reconfiguration is
* scheduled, then the undo is scheduled for later processing as
* usual, but if another reconfiguration is already scheduled, then
* such reconfiguration is removed instead (i.e. undo is applied on
* the last commit that scheduled it).
*
* Result: %CONF_DONE if the configuration has been accepted immediately,
* %CONF_PROGRESS if it will take some time to switch to it, %CONF_QUEUED
* if it's been queued due to another reconfiguration being in progress now,
* %CONF_UNQUEUED if a scheduled reconfiguration is removed, %CONF_NOTHING
* if there is no relevant configuration to undo (the previous config request
* was config_undo() too) or %CONF_SHUTDOWN if BIRD is in shutdown mode and
* no new configuration changes are accepted.
*/
int
config_undo(void)
{
if (shutting_down)
return CONF_SHUTDOWN;
if (!undo_available || !old_config)
return CONF_NOTHING;
undo_available = 0;
tm_stop(config_timer);
if (configuring)
{
config_event = ev_new(&root_pool);
config_event->hook = config_done;
if (future_cftype)
{
config_free(future_config);
future_config = NULL;
log(L_INFO "Removing queued configuration");
future_cftype = RECONFIG_NONE;
return CONF_UNQUEUED;
}
else
{
log(L_INFO "Queueing undo configuration");
future_cftype = RECONFIG_UNDO;
return CONF_QUEUED;
}
}
if (config_do_commit(NULL, RECONFIG_UNDO))
{
config_done(NULL);
return CONF_DONE;
}
return CONF_PROGRESS;
}
extern void cmd_reconfig_undo_notify(void);
static void
config_timeout(struct timer *t)
{
log(L_INFO "Config timeout expired, starting undo");
cmd_reconfig_undo_notify();
int r = config_undo();
if (r < 0)
log(L_ERR "Undo request failed");
}
void
config_init(void)
{
config_event = ev_new(&root_pool);
config_event->hook = config_done;
config_timer = tm_new(&root_pool);
config_timer->hook = config_timeout;
}
/**
* order_shutdown - order BIRD shutdown
*
......@@ -328,15 +458,16 @@ order_shutdown(void)
if (shutting_down)
return;
log(L_INFO "Shutting down");
c = lp_alloc(config->mem, sizeof(struct config));
memcpy(c, config, sizeof(struct config));
init_list(&c->protos);
init_list(&c->tables);
c->shutdown = 1;
config_commit(c, RECONFIG_HARD, 0);
shutting_down = 1;
config_commit(c, RECONFIG_HARD);
shutting_down = 2;
}
/**
......
......@@ -54,28 +54,33 @@ struct config {
/* Please don't use these variables in protocols. Use proto_config->global instead. */
extern struct config *config; /* Currently active configuration */
extern struct config *new_config; /* Configuration being parsed */
extern struct config *old_config; /* Old configuration when reconfiguration is in progress */
extern struct config *future_config; /* New config held here if recon requested during recon */
extern int shutting_down;
extern bird_clock_t boot_time;
struct config *config_alloc(byte *name);
int config_parse(struct config *);
int cli_parse(struct config *);
void config_free(struct config *);
int config_commit(struct config *, int type);
#define RECONFIG_HARD 0
#define RECONFIG_SOFT 1
int config_commit(struct config *, int type, int timeout);
int config_confirm(void);
int config_undo(void);
void config_init(void);
void cf_error(char *msg, ...) NORET;
void config_add_obstacle(struct config *);
void config_del_obstacle(struct config *);
void order_shutdown(void);
#define CONF_DONE 0
#define CONF_PROGRESS 1
#define CONF_QUEUED 2
#define CONF_SHUTDOWN 3
#define RECONFIG_NONE 0
#define RECONFIG_HARD 1
#define RECONFIG_SOFT 2
#define RECONFIG_UNDO 3
#define CONF_DONE 0
#define CONF_PROGRESS 1
#define CONF_QUEUED 2
#define CONF_UNQUEUED 3
#define CONF_CONFIRM 4
#define CONF_SHUTDOWN -1
#define CONF_NOTHING -2
/* Pools */
......
......@@ -10,6 +10,9 @@ m4_divert(-1)m4_dnl
m4_define(CF_CLI, `m4_divert(0){ "m4_translit($1,A-Z,a-z)", "$3", "$4", 1 },
m4_divert(-1)')
m4_define(CF_CLI_CMD, `m4_divert(0){ "m4_translit($1,A-Z,a-z)", "$2", "$3", 1 },
m4_divert(-1)')
m4_define(CF_CLI_HELP, `m4_divert(0){ "m4_translit($1,A-Z,a-z)", "$2", "$3", 0 },
m4_divert(-1)')
......
......@@ -44,6 +44,7 @@ m4_define(CF_CLI, `m4_define([[CF_cmd]], cmd_[[]]m4_translit($1, [[ ]], _))DNL
m4_divert(2)CF_KEYWORDS(m4_translit($1, [[ ]], [[,]]))
m4_divert(3)CF_ADDTO(cli_cmd, CF_cmd)
CF_cmd: $1 $2 END')
m4_define(CF_CLI_CMD, `')
m4_define(CF_CLI_HELP, `')
# ENUM declarations are ignored
......
......@@ -702,19 +702,48 @@ This argument can be omitted if there exists only a single instance.
<tag>flush roa [table <m/t/>]</tag>
Remove all dynamic ROA entries from a ROA table.
<tag>configure [soft] ["<m/config file/"]</tag>
<tag>configure [soft] ["<m/config file/"] [timeout [<m/num/]]</tag>
Reload configuration from a given file. BIRD will smoothly
switch itself to the new configuration, protocols are
reconfigured if possible, restarted otherwise. Changes in
filters usually lead to restart of affected protocols. If
<cf/soft/ option is used, changes in filters does not cause
filters usually lead to restart of affected protocols.
If <cf/soft/ option is used, changes in filters does not cause
BIRD to restart affected protocols, therefore already accepted
routes (according to old filters) would be still propagated,
but new routes would be processed according to the new
filters.
If <cf/timeout/ option is used, config timer is activated. The
new configuration could be either confirmed using
<cf/configure confirm/ command, or it will be reverted to the
old one when the config timer expires. This is useful for cases
when reconfiguration breaks current routing and a router becames
inaccessible for an administrator. The config timeout expiration is
equivalent to <cf/configure undo/ command. The timeout duration
could be specified, default is 300 s.
<tag>configure confirm</tag>
Deactivate the config undo timer and therefore confirm the current
configuration.
<tag>configure undo</tag>
Undo the last configuration change and smoothly switch back to
the previous (stored) configuration. If the last configuration
change was soft, the undo change is also soft. There is only
one level of undo, but in some specific cases when several
reconfiguration requests are given immediately in a row and
the intermediate ones are skipped then the undo also skips them back.
<tag>configure check ["<m/config file/"]</tag>
Read and parse given config file, but do not use it. useful
for checking syntactic and some semantic validity of an config
file.
<tag>enable|disable|restart <m/name/|"<m/pattern/"|all</tag>
Enable, disable or restart a given protocol instance, instances matching the <cf><m/pattern/</cf> or <cf/all/ instances.
Enable, disable or restart a given protocol instance,
instances matching the <cf><m/pattern/</cf> or
<cf/all/ instances.
<tag>reload [in|out] <m/name/|"<m/pattern/"|all</tag>
......
......@@ -25,6 +25,12 @@ Reply codes of BIRD command-line interface
0014 Route count
0015 Reloading
0016 Access restricted
0017 Reconfiguration already in progress, removing queued config
0018 Reconfiguration confirmed
0019 Nothing to do (configure undo/confirm)
0020 Configuration OK
0021 Undo requested
0022 Undo scheduled
1000 BIRD version
1001 Interface list
......
......@@ -122,6 +122,7 @@ cli_printf(cli *c, int code, char *msg, ...)
va_list args;
byte buf[CLI_LINE_SIZE];
int cd = code;
int errcode;
int size, cnt;
if (cd < 0)
......@@ -131,16 +132,26 @@ cli_printf(cli *c, int code, char *msg, ...)
size = bsprintf(buf, " ");
else
size = bsprintf(buf, "%04d-", cd);
errcode = -8000;
}
else if (cd == CLI_ASYNC_CODE)
{
size = 1; buf[0] = '+';
errcode = cd;
}
else
size = bsprintf(buf, "%04d ", cd);
{
size = bsprintf(buf, "%04d ", cd);
errcode = 8000;
}
c->last_reply = cd;
va_start(args, msg);
cnt = bvsnprintf(buf+size, sizeof(buf)-size-1, msg, args);
va_end(args);
if (cnt < 0)
{
cli_printf(c, code < 0 ? -8000 : 8000, "<line overflow>");
cli_printf(c, errcode, "<line overflow>");
return;
}
size += cnt;
......@@ -385,12 +396,17 @@ cli_echo(unsigned int class, byte *msg)
}
}
/* Hack for scheduled undo notification */
extern cli *cmd_reconfig_stored_cli;
void
cli_free(cli *c)
{
cli_set_log_echo(c, 0, 0);
if (c->cleanup)
c->cleanup(c);
if (c == cmd_reconfig_stored_cli)
cmd_reconfig_stored_cli = NULL;
rfree(c->pool);
}
......
......@@ -49,6 +49,8 @@ typedef struct cli {
extern pool *cli_pool;
extern struct cli *this_cli; /* Used during parsing */
#define CLI_ASYNC_CODE 10000
/* Functions to be called by command handlers */
void cli_printf(cli *, int, char *, ...);
......
......@@ -14,6 +14,9 @@
#include "lib/string.h"
#include "lib/resource.h"
extern int shutting_down;
extern int configuring;
void
cmd_show_status(void)
{
......@@ -27,9 +30,10 @@ cmd_show_status(void)
cli_msg(-1011, "Last reboot on %s", tim);
tm_format_datetime(tim, &config->tf_base, config->load_time);
cli_msg(-1011, "Last reconfiguration on %s", tim);
if (shutting_down)
cli_msg(13, "Shutdown in progress");
else if (old_config)
else if (configuring)
cli_msg(13, "Reconfiguration in progress");
else
cli_msg(13, "Daemon is up and running");
......
......@@ -516,7 +516,7 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty
p->down_code = nc->disabled ? PDC_CF_DISABLE : PDC_CF_RESTART;
p->cf_new = nc;
}
else if (!shutting_down)
else if (!new->shutdown)
{
log(L_INFO "Removing protocol %s", p->name);
p->down_code = PDC_CF_REMOVE;
......@@ -537,7 +537,7 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty
WALK_LIST(nc, new->protos)
if (!nc->proto)
{
if (old_config) /* Not a first-time configuration */
if (old) /* Not a first-time configuration */
log(L_INFO "Adding protocol %s", nc->name);
proto_init(nc);
}
......
......@@ -14,9 +14,9 @@ CF_HDR
CF_DECLS
CF_KEYWORDS(LOG, SYSLOG, ALL, DEBUG, TRACE, INFO, REMOTE, WARNING, ERROR, AUTH, FATAL, BUG, STDERR, SOFT)
CF_KEYWORDS(TIMEFORMAT, ISO, SHORT, LONG, BASE, NAME)
CF_KEYWORDS(TIMEFORMAT, ISO, SHORT, LONG, BASE, NAME, CONFIRM, UNDO, CHECK, TIMEOUT)
%type <i> log_mask log_mask_list log_cat
%type <i> log_mask log_mask_list log_cat cfg_timeout
%type <g> log_file
%type <t> cfg_name
%type <tf> timeformat_which
......@@ -104,13 +104,26 @@ timeformat_base:
/* Unix specific commands */
CF_CLI_HELP(CONFIGURE, [soft] [\"<file>\"], [[Reload configuration]])
CF_CLI_HELP(CONFIGURE, ..., [[Reload configuration]])
CF_CLI(CONFIGURE, cfg_name, [\"<file>\"], [[Reload configuration]])
{ cmd_reconfig($2, RECONFIG_HARD); } ;
CF_CLI(CONFIGURE, cfg_name cfg_timeout, [\"<file>\"] [timeout [<sec>]], [[Reload configuration]])
{ cmd_reconfig($2, RECONFIG_HARD, $3); } ;
CF_CLI(CONFIGURE SOFT, cfg_name, [\"<file>\"], [[Reload configuration and ignore changes in filters]])
{ cmd_reconfig($3, RECONFIG_SOFT); } ;
CF_CLI(CONFIGURE SOFT, cfg_name cfg_timeout, [\"<file>\"] [timeout [<sec>]], [[Reload configuration and ignore changes in filters]])
{ cmd_reconfig($3, RECONFIG_SOFT, $4); } ;
/* Hack to get input completion for 'timeout' */
CF_CLI_CMD(CONFIGURE TIMEOUT, [<sec>], [[Reload configuration with undo timeout]])
CF_CLI_CMD(CONFIGURE SOFT TIMEOUT, [<sec>], [[Reload configuration with undo timeout]])
CF_CLI(CONFIGURE CONFIRM,,, [[Confirm last configuration change - deactivate undo timeout]])
{ cmd_reconfig_confirm(); } ;
CF_CLI(CONFIGURE UNDO,,, [[Undo last configuration change]])
{ cmd_reconfig_undo(); } ;
CF_CLI(CONFIGURE CHECK, cfg_name, [\"<file>\"], [[Parse configuration and check its validity]])
{ cmd_check_config($3); } ;
CF_CLI(DOWN,,, [[Shut the daemon down]])
{ cmd_shutdown(); } ;
......@@ -120,6 +133,12 @@ cfg_name:
| TEXT
;
cfg_timeout:
/* empty */ { $$ = 0; }
| TIMEOUT { $$ = UNIX_DEFAULT_CONFIGURE_TIMEOUT; }
| TIMEOUT expr { $$ = $2; }
;
CF_CODE
CF_END
......@@ -121,7 +121,7 @@ static list near_timers, far_timers;
static bird_clock_t first_far_timer = TIME_INFINITY;
/* now must be different from 0, because 0 is a special value in timer->expires */