Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live reconfiguration #851

Merged
merged 53 commits into from
Dec 29, 2017
Merged

Live reconfiguration #851

merged 53 commits into from
Dec 29, 2017

Conversation

vankoven
Copy link
Contributor

Live reconfiguration, only core features are supported in reconfiguration:

  • Adding and removing server groups
  • adding and removing servers in server groups
  • adding more connections to each server (removing of connection are not supported)
  • adding and removing http match rules
  • changing any server group options
  • changing scheduler assigned to a server group

Known limitations/issues:

  • can't reduce number of server connections

To start live reconfiguration simply run ./scripts/tempesta.sh --reload or write sysctl variable sysctl -w net.tempesta.state=start while Tempesta FW is running.

keshonok and others added 17 commits October 17, 2017 17:27
A server group was added to tempesta's internal lists before it was
fully filled with data and before the data was verified. The patch
separates these steps. A server group is filled with data first,
the data is verified, and only then the group is added to Tempesta.
'call_counter' served two functions. One was to mark specs of
directives that were found in configuration file, and attempt
to apply default values to directives whose specs were not
marked. The other was to indicate specs of directives that
required a cleanup as memory may had been allocated. However,
`call_counter` was cleared in some cases (read: nested specs),
which broke the function of a cleanup.

Two different flags are created __called_now and __called_ever,
each serving its specific function.
That makes it so much easier to navigate, analyze, and modify.
The patch makes it possible to "start" Tempesta when it's running
already. With that, a new configuration file may be supplied,
provided that Tempesta can handle a live reconfiguration.
Tempesta can now handle multiple consecutive starts. In case of
errors it cleans up correctly.

The actual live reconfiguration of any directive is not there yet.
The function is called after complete configuration file is parsed
and pushed to all modules. It gives the opportunity to cross-check
configuration data, or set up other data based on configuration
data before the system is started.
@@ -35,7 +35,7 @@ tfw_mod=tempesta_fw
tfw_sched_mod=tfw_sched_$sched
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update copyright year at the above

--reload)
reload
exit
;;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move Build, Run & Stop and Configuration sections from README.md to the Wiki (leaving reference to the Wiki section(s) in README.md) and add description of the new command.

return -EINVAL;
}

ret = spec_handle_entry(matching_spec, &ps->e);
entry_reset(&ps->e);
if (ret)
return ret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we return here or at the above, who calls entry_reset(ps), i.e. free()'s all the entry items? I didn't get the sense of 71545e6 (how the change is linked with 'show errors'), but it seems the change introduces memory leak.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entry_reset() is called here:

entry_reset(&ps.e);

The 71545e6 commit is for showing erroneous line number and line content for nested directives in print_parse_error(). E.g. for the config:

srv_group A {
        server 127.0.0.1:8000 error_string;
}

Current master will produce the next error message:

[tempesta] ERROR: Invalid number of arguments: 2
[tempesta] ERROR: configuration parsing error

While the current branch gives more helpful error message:

[tempesta] ERROR: Invalid number of arguments: 2
[tempesta] ERROR: configuration parsing error:
  2:         server 127.0.0.1:8000 error_string;
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

__tfw_sg_for_each(&sg_list_reconfig, list_reconfig);
}
else {
__tfw_sg_for_each(&sg_list, list);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In all use cases tfw_sg_for_each_srv() is called with true or false as reconfig value, so actually there are two functions aggregated together with the boolean argument. It's better to write 2 normal functions with only one argument.

I'm not sure whether it has sense to move 2-lines loop to the macro, but the macro makes the function bit more complex: the first impression is that cb is unused, moreover since the macro is global it seems like it's used also in some other place but actually it isn't. Please move the macro definition into the funcrion with undef at the end of the function or just replace the macro calls by the loop - not a big deal to copy & paste 2 lines of code.

*/
TfwSrvGroup *
tfw_sg_lookup(const char *name)
__tfw_sg_lookup(const char *name, bool reconfig)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is always called with explicit true or false as a value of reconfig, so please split the function to two real functions.

@@ -79,10 +81,11 @@ typedef struct {
*/
struct tfw_srv_group_t {
struct list_head list;
struct list_head list_reconfig;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please comment the item above. Need to answer why list isn't enough, i.e. when a group can be referenced from sg_list and be in sg_list_reconfig at the same time.


/* Some servers was removed, so scheduler update is required */
if (sg_cfg->orig_sg &&
(sg_cfg->orig_sg->srv_n != sg_cfg->parsed_sg->srv_n)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please place opening curly braces on next line for complex if conditions

list_for_each_entry(srv, &sg_cfg->parsed_sg->srv_list, list) {
if (tfw_addr_eq(&srv->addr, &orig_srv->addr))
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need curly braces

/*
* TODO: shrink connections: disconnect connection and remove
* it from server's conn_list
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bad thing to leave server connection open - it could lead to serious memory leakage. Why don't you use tfw_sock_srv_disconnect()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function was called later in one loop for all removed servers. I've reworked this to call disconnect() as soon as possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't understand your comment: if you reworked the code, then why the condition and the comment are still here?

int r;

INIT_LIST_HEAD(&sg_cfg_list);
if ((r = tfw_cfgop_new_sg_cfg_default()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems it fixes #748. If so, please mark 1ab95a2 as the fix for the issue, so it will be closed on merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually no. Default server group is created to store various server group options as global defaults. Implicit default server group is not registered as server group available after reconfig until cfgend().

* invalid.
*
* On configuration parsing stage binary representation of a server group is
* saved into @parsed_sg, while @orig_sg poins to the existing server group
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

poins -> points

tfw_http_match_list_free(tfw_rules_reconfig);
tfw_rules_reconfig = NULL;

if (tfw_runstate_is_reconfig())
Copy link
Contributor

@aleksostapenko aleksostapenko Nov 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I correctly understand - this code will be executed after failed reconfiguration (on parse stage). And in this case Tempesta should continue working with old configuration, but we release old tfw_rules here. Maybe condition should be inverted...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're absolutely right, i'll fix that

{
TfwServer *srv;

write_lock(&sg->lock);
Copy link
Contributor

@aleksostapenko aleksostapenko Dec 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that read_lock/unlock is not used anywhere with sg->lock - maybe using spin_lock with sg->lock will be simpler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, frankly speaking I don't see write operation here - this is just a list scan which is supposed to be a read operation. It seems read lock should be used here.

}

/**
* Iterate over all server groups of given server grop @sg and call @cb for
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

server groups -> servers
grop - > group

@@ -202,7 +395,26 @@ __tfw_sg_for_each_srv(TfwSrvGroup *sg, int (*cb)(TfwServer *srv))
}

/**
* Iterate over all server groups and call @cb for each server.
* Iterate over all the acive server groups and call @cb for each server group.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

acive -> active

.name = "sticky_sessions",
.deflt = TFW_CFG_DFLT_VAL,
.handler = tfw_cfgop_in_sticky_sess,
.cleanup = tfw_cfgop_cleanup_srv_groups,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand cleanup callbacks is not called for nested srv_group options specs. Does it make sense to define these callbacks?

* stage since the new configuration is provided by a user and may contain
* errors.
* - applying stage: tfw_sock_srv_start(). Errors are unrecoverable and mean
* that only a part of changes was appied, so the resulting configuration is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appied -> applied

return -EINVAL;
tfw_cfg_out_slstsz++;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If in config file a server outside any group is defined at first and then default group is defined explicitly - is this an acceptable case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not acceptable. Once a server is defined outside of any group, default group will be registered as available after reconfiguration. If the explicit default group is defined after that, tfw_cfgop_begin_srv_group() will throw a Group '...' already exists error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - got it. Thanks.

setup_timer(&srv->gs_timer, tfw_sock_srv_grace_shutdown_cb,
(unsigned long)srv);
mod_timer(&srv->gs_timer, jiffies + tfw_cfg_grace_time * HZ);
tfw_sock_srv_grace_list_add(srv);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems safer to place tfw_sock_srv_grace_list_add(srv) before mod_timer(&srv->gs_timer, ...).

@@ -41,20 +41,30 @@ typedef struct tfw_scheduler_t TfwScheduler;
* Server descriptor, a TfwPeer successor.
*
* @list - member pointer in the list of servers of a server group;
* @gs_timmer - grace shutdown timer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gs_timmer -> gs_timer

Copy link
Contributor

@krizhanovsky krizhanovsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to merge after small fixes and cleanups (Yahoo!)


tfw_http_conn_evict_timeout(srv_conn, &equeue);
if (unlikely(resched))
tfw_http_conn_collect(srv_conn, &sch_queue, &equeue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't about this particular patch set, but is about the whole recent changes: just too many list traversals and many of them are under lock. Such these two functions both traverse forward list: one to evict timed out requests and the second one to collect the rest of them. Why not to do this in one shot? Let's leave it for #687 .

else if (orig_srv->conn_n > srv->conn_n) {
/*
* TODO: shrink number of connections. Disconnects are performed
* asynchronously, can't destroy connection here and now.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still didn't get why for example we can't schedule the connections for eviction, i.e. it's still unclear why we can't implement the logic now and have to leave with the TODO. Please add a requirement to #687 - let's put the TODOs from the PR there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we need to shrink number of connections to a given server, we must do two things: disconnect and destroy some connections; and update scheduler data for that server. The scheduler data is normally common for the whole server group.

Disconnecting of connections is an asynchronous operation. So we have to keep connections in srv->conn_list until disconnect is completed. In the same time right after scheduling for disconnects we use all connections from srv->conn_list to build scheduler data. Dead connections poison scheduler, since number of scheduling attempts is limited.

UPD: Somehow i missed a possible solution: we can check TFW_CONN_B_DEL flag when building scheduler data to skip connections scheduled to disconnect. Any way that can be done as a separate task.

* @tfw_cfg_sg_def since options of explicitly defined 'default' group must not
* be changed when user updates default server group options.
*/
static TfwCfgSrvGroup *tfw_cfg_sg_opts = NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if there is a functional test for #748? The issue has clear configuration example, but I couldn't quickly find the example in the tests.

The problem with the current test system is that all the tested scenarios are expressed in Python and it's quite difficult to find a particular case. Meantime, e.g. MariaDB uses SQL-like (the language of the pruduct) templates to express the test cases, for example https://github.com/tempesta-tech/mariadb/blob/trunk/mysql-test/suite/versioning/t/select.test . If you just familiar with SQL you can quickly get an idea which case is covered by the test.

Can we do the same for out test system? It'd be great to have some configuration templates with some high level commands like start Nginx with some configuration, start N instances of ab and so on. @intelfx @vladtcvs opinions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test functional/reconf/test_stress_sched_http.py covers #748 and reconfiguration of http scheduler in the same time.

{
TfwServer *srv;

write_lock(&sg->lock);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, frankly speaking I don't see write operation here - this is just a list scan which is supposed to be a read operation. It seems read lock should be used here.

tfw_sg_destroy(TfwSrvGroup *sg)
{
TFW_DBG2("release group: '%s'\n", sg->name);
BUG_ON(!list_empty(&sg->srv_list));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we definitely crash on this condition? We saw many crashes due to such checks and some of them don't actually mean inevitable crashes. It's better to use WARN() to notify us (developers) or a user about some problem. Also note https://github.com/tempesta-tech/linux-4.9.35-tfw/blob/master/include/asm-generic/bug.h#L37 . @aleksostapenko also please note this.

/* Server was not found in new configuration. */
if (!(srv->flags & TFW_CFG_M_ACTION)) {
if ((r = tfw_sock_srv_grace_shutdown_srv(sg, srv)))
return r;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to release sg->lock

}
else if (srv->flags & TFW_CFG_F_MOD)
if ((r = tfw_cfgop_update_srv(srv, sg_cfg)))
goto err;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to release sg->lock

while (1)
{
TfwServer *srv;
int act;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please place open curly brace on a next line only in case of complex multi-line conditions and place it on the same line in all other cases.

Do you really need act variable? Doesn't

                if (del_timer_sync(&srv->gs_timer))                                   
                        tfw_sock_srv_grace_stop(srv);

look simpler?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants