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

Fix #672: Add health monitoring for backend servers. #877

Merged
merged 30 commits into from
Mar 4, 2018
Merged

Conversation

aleksostapenko
Copy link
Contributor

No description provided.

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 several clenups. Don't forget to close #683 since it isn't mentioned in the patch title.

},
{}
{ 0 }
Copy link
Contributor

Choose a reason for hiding this comment

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

I just noticed that APM configuration isn't documented on our Wiki at all, so please update https://github.com/tempesta-tech/tempesta/wiki/Configuration and https://github.com/tempesta-tech/tempesta/wiki/Performance-monitoring chapters. The last one also requires description on the logic from tasks #672 and #683, however probably the chapter for backend servers also must be updated.

Also please create a test issues for #672 and #683. The test must at least test following:

  1. all the new configuration options
  2. a server returning error response must not be scheduled, but only for configured error responses
  3. after some time, when server stops return error responses, it receives requests again
  4. procfs statistic must be verified for enabled and disabled server states

Copy link
Contributor Author

@aleksostapenko aleksostapenko Feb 12, 2018

Choose a reason for hiding this comment

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

char *req;
unsigned long reqsz;
u32 crc32;
unsigned short tmt;
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 the members to minimize alignment holes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

atomic_t rearm;
} TfwApmHMCtl;

/* Entry for configureation of separate health monitors. */
Copy link
Contributor

Choose a reason for hiding this comment

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

"configureation' -> "configuration"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected

INIT_LIST_HEAD(&hm->msg.seq_list);
INIT_LIST_HEAD(&((TfwHttpReq *)hm)->fwd_list);
INIT_LIST_HEAD(&((TfwHttpReq *)hm)->nip_list);
hm->destructor = tfw_http_req_destruct;
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 partial copy & paste from tfw_http_msg_alloc(). Moreover, it's body is generic enough, but it's named with "hwmonitor" suffix. Please unify it with tfw_http_msg_alloc().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

BUG_ON(!srv->apmref);
hmctl = &((TfwApmData *)srv->apmref)->hmctl;
list_for_each_entry(hm, &tfw_hm_list, list) {
if(!strcasecmp(name, hm->name)) {
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 a space between if and opening brace. Also it's better to write such loops like

if (strcasecmp(name, hm->name))
     continue;
// the if body

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

if (!req->conn) {
tfw_http_msg_free((TfwHttpMsg *)req);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Later it'll be unclear what requests without a connection are, so please write a comment, or, better since there are a few such places, write a small function for the requests deletion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected. Replaced these pointer checks with TFW_HTTP_HMONITOR request flag verification, according to #877 (comment).


return 0;
}
EXPORT_SYMBOL(tfw_str_crc32_calc);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add an appropriate unit test to t/unit/test_tfw_str.c

Copy link
Contributor Author

@aleksostapenko aleksostapenko Jan 18, 2018

Choose a reason for hiding this comment

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

Removed this function. tfw_str_to_cstr() used instead.

Copy link
Contributor

@vankoven vankoven left a comment

Choose a reason for hiding this comment

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

Mostly the patch is good, but i don't really like new hmonitor parameter in scheduling functions and frequent checks for requests without origin connections. This should be discussed in comments before merging

@@ -372,6 +373,103 @@ tfw_stats_update(TfwPcntRanges *rng, unsigned int r_time)
return;
}

/* Time granularity for HTTP codes accounting during health monitoring. */
#define HM_FREQ 10

Copy link
Contributor

Choose a reason for hiding this comment

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

Extra empty line is not needed here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.


BUG_ON(srv->apmref);

if (!(data = tfw_apm_create()))
if (!(data = tfw_apm_create(hm)))
return -ENOMEM;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a real issue, just a notice. tfw_apm_create() may fail due to improper input values (hm, tfw_hm_codes_cnt) in addition to lack of free memory. You can pass actual error code in pointer by using ERR_PTR() macro and check it using IS_ERR() macro. It's widely used across kernel and TempestaFW code.

Copy link
Contributor Author

@aleksostapenko aleksostapenko Jan 18, 2018

Choose a reason for hiding this comment

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

Removed hm argument from tfw_apm_create(), due to reconfiguration changes.

BUG_ON(!hmstats);
for (i = 0; i < tfw_hm_codes_cnt; ++i) {
if (hmstats[i].hmcfg->code == status ||
hmstats[i].hmcfg->code == (int)(status / 100))
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 not clear for the first sight why we must take into account the first digit in status (status / 100), comment is required here. Also documentation of code field of the TfwApmHMCfg should be extended to cover all possible values: 4* and 5* masks in addition to exact status codes.

Explicit casting to int is not requited since status is already int.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.


if (tfw_http_parse_status(ce->vals[0], &code)) {
TFW_ERR_NL("Unable to parse http code value: '%s'\n",
ce->vals[0] ? ce->vals[0] : "No value specified");
Copy link
Contributor

Choose a reason for hiding this comment

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

ce->vals[0|1|2] is non NULL if tfw_cfg_check_val_n(ce, 3) conditions has met.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

@@ -1179,6 +1235,13 @@ tfw_http_conn_resched(struct list_head *sch_queue, struct list_head *equeue)
" an available back end server");
continue;
}

if (!req->conn && sch_conn->peer != prev_conn->peer) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Checking for req->conn here and in many other places seems a little bit hacky. Isn't it better to introduce some flag for TfwHttpReq to indicate that the request was issued by TempestaFW it self and not by the end user.
With that flag you can also remove newly added hmonitor operand in sched_srv_conn() functions.

TempestaFW will generate more requests form it's own in the future, e.g. conditional requests to backend servers, so it's likely that we will need some way to distinguish requests from Tempesta and end clients.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected. TFW_HTTP_HMONITOR request flag introduced.

@@ -1179,6 +1235,13 @@ tfw_http_conn_resched(struct list_head *sch_queue, struct list_head *equeue)
" an available back end server");
continue;
}

if (!req->conn && sch_conn->peer != prev_conn->peer) {
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 better to find out first is the request from client or from hmonitor and then chose the right scheduling function: generic scheduling (can be slow) or scheduling to the same server (the fastest way).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

unsigned long len = 0;

BUG_ON(str->len && !str->ptr);
data = p = kmalloc(str->len, GFP_ATOMIC);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use tfw_str_to_cstr() here?

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, missed that handy function. Thanks.
Corrected.

BUG_ON(list_empty(&tfw_hm_list));
if (!tfw_hm_entry->codes && !tfw_hm_entry->crc32) {
TFW_ERR_NL("Response codes and crc32 values are not"
" configured for '%s'\n", cs->name);
Copy link
Contributor

Choose a reason for hiding this comment

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

The error message is confusing and doesn't report the actual reason for the error. Both of these configuration options are marked as allow_none which means that they can be skipped. So the actual error here, I believe, is that at least one of these (or both, for that matter) must be configured.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@keshonok Thank you for review.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

},
{
.name = "request_url",
.deflt = 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 believe, that If the request option has a default value, then it's logical that this option should have a default value as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

tfw_hm_entry->req = (char *)__get_free_pages(GFP_KERNEL,
get_order(size));
if (!tfw_hm_entry->req) {
TFW_ERR_NL("Can't allocate memory for helth"
Copy link
Contributor

Choose a reason for hiding this comment

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

helth -> health

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.


if (tfw_cfg_check_val_n(ce, 1))
return -EINVAL;

Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that these two checks are congenerical - about a very similar thing, simple, and logically in the same realm. Usually there's no need for an empty line between checks like these as they are not logically separated. There's lot of examples in the code for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected (and two cases below too).


if (tfw_cfg_check_val_n(ce, 3))
return -EINVAL;

Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that these two checks are congenerical - about a very similar thing, simple, and logically in the same realm. Usually there's no need for an empty line between checks like these as they are not logically separated. There's lot of examples in the code for this.


if (tfw_cfg_check_single_val(ce))
return -EINVAL;

Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that these two checks are congenerical - about a very similar thing, simple, and logically in the same realm. Usually there's no need for an empty line between checks like these as they are not logically separated. There's lot of examples in the code for this.

}
hm->h_tbl->size = __HHTBL_SZ(1);
hm->h_tbl->off = TFW_HTTP_HDR_RAW;
memset(hm->h_tbl->tbl, 0, __HHTBL_SZ(1) * sizeof(TfwStr));
Copy link
Contributor

Choose a reason for hiding this comment

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

These changes leave a strange feeling of being half-done. On one hand, the new argument allows for creating of special messages without the header table that is required for the parser. On the other hand, the parser is still initialized regardless of that in the code that follows. Then, there's tfw_http_msg_alloc_err_resp() that basically does the same thing but it's still there and not removed.

Now the new argument is required for all allocations, and it looks somewhat ugly.

I believe this (and related) code should be refactored. The original top-level interface should stay the same in the form of tfw_http_msg_alloc(int type). A different interface should be introduced for messages that are not going through the parser AND are not modified - which are the messages that are fully prepared from scratch by Tempesta. Perhaps, internally, these two different sets of interfaces may use the same common code - either by calling some inline functions or with macros. tfw_http_msg_alloc_err_resp() would be superseded with the new interface.

Perhaps, you could call it tfw_http_msg_alloc_light() - nothing better comes to mind right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -206,6 +208,24 @@ tfw_srvstats_seq_show(struct seq_file *seq, void *off)
atomic64_read(&srv->sess_n));
seq_printf(seq, "Total schedulable connections\t: %zd\n",
srv->conn_n - rc);

seq_printf(seq, "HTTP health monitor is enabled\t: %d\n", hm);
if (hm) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps, hm_stats should be declared within this block as it's not used elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

@@ -64,9 +65,15 @@ typedef struct {
atomic64_t refcnt;
unsigned int weight;
unsigned int flags;
unsigned int hm_flags;
Copy link
Contributor

@keshonok keshonok Jan 19, 2018

Choose a reason for hiding this comment

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

Correct me if I am wrong, but my understanding is that both atomic and non-atomic ops can be used on the same variable. There's only a handful of flags, and it's a 32-bit variable. So, perhaps, a separate hm_flags is unnecessary?

Also, I am not sure I understand what's going on here. For atomic access you need an unsigned long variable, but this is an unsigned int. Everywhere in the code it's cast to unsigned long, but I believe that it's incorrect as it would extend to memory occupied by other unrelated data.

Copy link
Contributor

Choose a reason for hiding this comment

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

We can not mix atomic and non-atomic access to the same data: if one operation doesn't lock the system bus, then it just writes back it's outdated view to the memory location. I agree with wrong type conversions. Also it seems the atomicity on the flags adjustment is broken, see comment for tfw_srv_mark_alive().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In current implementation of set_bit/clear_bit functions for x86, in cases of constant bit index argument - per-byte instructions orb and andb are used. So if for bit index we use immediate value less then 32 - we will not go beyond the int boarders. But yes, this is not very safe approach.
Corrected.

}
else
tfw_http_req_error(srv_conn, req, equeue, 500,
"request dropped: forwarding error");
Copy link
Contributor

Choose a reason for hiding this comment

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

Curly braces are required here according to the coding standard (because the other branch has them).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

tfw_http_msg_free((TfwHttpMsg *)req);
return;
}

if (reply) {
TfwCliConn *cli_conn = (TfwCliConn *)req->conn;
tfw_connection_unlink_msg(req->conn);
Copy link
Contributor

Choose a reason for hiding this comment

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

Not visible here, but curly braces are required here in the else branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

tfw_connection_unlink_msg(resp->conn);
tfw_apm_update(((TfwServer *)resp->conn->peer)->apmref,
resp->jrxtstamp,
resp->jrxtstamp - req->jtxtstamp);
Copy link
Contributor

Choose a reason for hiding this comment

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

This line fits perfectly in the preceding line of code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

"request dropped: forwarding error");
if (req->flags & TFW_HTTP_HMONITOR) {
tfw_http_req_delist(srv_conn, req);
tfw_http_msg_free((TfwHttpMsg *)req);
Copy link
Contributor

Choose a reason for hiding this comment

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

So, if we're unable to send a health monitoring request, then it's simply dropped without any trace or action upon that? Is that the intended behaviour?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, maybe having a warning here would be good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Warning added.

memcpy(health->name, hname, size);
srv = orig_srv ? : new_srv;
} else {
srv = orig_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's difficult to understand the variety of combinations of function's arguments without comments. orig_srv may be NULL. The code suggest it's definitely NOT NULL when !hname. Why is that? A proper comment would be helpful.

Copy link
Contributor Author

@aleksostapenko aleksostapenko Feb 7, 2018

Choose a reason for hiding this comment

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

This code has been redesigned.

static int
tfw_cfg_srv_set_health(void)
{
TfwSrvHealth *hth;
Copy link
Contributor

Choose a reason for hiding this comment

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

There're some common shortcuts that are understood in a specific way. For instance, nth usually mean n-th consecutive number, or something like that. Of course you can name variables any way you like in a small function, but it's best if the name resemble an unambiguous meaning.

There's a number of "rules" for creating short names, one of which is removing all vowels: default -> dflt, etc. With that, it would be health -> hlth. Or just leave it health, it's sufficiently short in this particular case. Or even just h - as long as it can't be confused with h-th number or something. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code has been redesigned.

return false;
}
return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps, this can be made a simple static const array to avoid unnecessary run-time branching?

ce->vals[0]
? ce->vals[0]
: "No value specified");
ce->vals[0]);
Copy link
Contributor

@keshonok keshonok Jan 19, 2018

Choose a reason for hiding this comment

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

Can't this message be made clearer? What's 'value'? Perhaps, 'HTTP status code'?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

return;
}
tfw_str_to_cstr(&resp->body, body_cstr, resp->body.len + 1);
crc32 = crc32(0, body_cstr, resp->body.len);
Copy link
Contributor

Choose a reason for hiding this comment

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

Frankly, this looks like extra unnecessary work. The whole body of a response is copied to memory allocated from pool just to calculate CRC32 on it.

This is a response to a health monitoring request, so I guess it's relatively small, and because it comes from a server it probably comes in minimal number of chunks (i.e. it's not sent one byte at a time). As far as I can see crc32() function is perfectly suited for calculating the checksum over data that is located in different chunks (the seed argument), so it should be straightforward to write a function that goes over string's chunks and calculates CRC32.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, definitely: with #76 in mind we can have ~1M sites with enabled health monitoring and the copyings can be harmful. We also shouldn't make an assumption about size of health checking response - this is 100% depends on a user preferences. In general, we put a lot of effort for zero-copying - there are plenty of TfwStr and skb routines processing data chunks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

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.

There are still several issues. hm_flags atomicity is still unclear. Also please write a wiki doc for the next review - it'd be good to review it as well.

BUG_ON(len != str->len);
return crc;
}
EXPORT_SYMBOL(tfw_str_crc32_calc);
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 must be tested in t/unit/test_tfw_str.c, at least that CRC32 calculated for the same data represented in single block and TfwStr's chunks have the same values. Also please calculate a checksum for some data using Linxu crc32 command (as a sysadmin is supposed to use it for calculating HTML file checksums) and use the checkusm in the test - our implementation and the utility must return the same values. Or, the better, to use the utility in the functional thest for HM.

Also please mention the Linux utility crc32 in the Wiki in an example for health monitoring settings.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

BUG_ON(str->len && !str->ptr);
TFW_STR_FOR_EACH_CHUNK(c, str, end) {
crc = crc32(crc, c->ptr, c->len);
len += c->len;//!!!
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there is no need to introduce the overhead in the function. There'd be too expensive to check the length consistency in all chunks traversing logic, so only strings creation functions should care about the consistency.

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, forgot to remove this debug code. Thanks!
Corrected.

'request "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";\n'
'request_url "/page.html";\n'
'resp_code 200;\n'
'resp_crc32 3456;\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 use more realistic HTML response and properly calcucalted, using crc32 Linux tool, CRC32 checksum. The test must verify that CRC32 is calculated by Tempesta FW correctly and that Tempesta FW catches bad responses with wrong CRC32 and marks the server as dead.

@ikoveshnikov and @vladtcvs should we enumerate somewhere all the test with stress options to run them in long running mode on the CI system?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. New functional test TestHealthMonitorCRCOnly (for separate CRC32 only verification) is added.

@@ -2879,8 +3044,10 @@ tfw_http_req_key_calc(TfwHttpReq *req)

req->hash = tfw_hash_str(&req->uri_path);

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 sense to fall down for !(req->flags & TFW_HTTP_HMONITOR) and calculate CRC32 over empty data, so just return here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.


BUG_ON(!srv->apmref);
BUG_ON(!hm);
BUG_ON(test_bit(TFW_SRV_B_HMONITOR, &srv->hm_flags));
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 the check should be WARN_ON_ONCE() since failing it desn't lead to the whole system crash, just to misbehaving health monitoring.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.


BUG_ON(!hm);
if (hm->codes && test_bit(HTTP_CODE_BIT_NUM(status), hm->codes))
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

So if a server return us a 'good' response code, then we consider it as alive and don't check the response body. However, missbehaving server can return us 200 responce code with messy response body. Only both the correct response code and the body checksum must be treated as success.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But in case of default auto monitor - it seems that we haven't any acceptable CRC32 value to examine.

Copy link
Contributor

@krizhanovsky krizhanovsky Feb 17, 2018

Choose a reason for hiding this comment

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

Please use crc32 of the first response, but in auto mode only.

However, for applications with dynamic content, it's good to be able to explicitly disable crc32 check. Probably, the easiest way to do this is just explicitly configure auto HM without crc32, e.g.

health_check auto {
	request		"GET / HTTTP/1.0\r\n\r\n";
	request_url	"/";
	resp_code	200;
	timeout		10;
}

Other possibility is to explicitly set auto value for crc32 field if a user want's auto calculated crc32, e.g.:

health_check auto {
	request		"GET / HTTTP/1.0\r\n\r\n";
	request_url	"/";
	resp_code	200;
        resp_crc32      auto;
	timeout		10;
}

Please update the Wiki about explicit auto configuration and automatic crc32 calculation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done (the last approach).

tfw_srv_suspended(TfwServer *srv)
{
return test_bit(TFW_SRV_B_HMONITOR, &srv->hm_flags)
&& test_bit(TFW_SRV_B_SUSPEND, &srv->hm_flags);
Copy link
Contributor

Choose a reason for hiding this comment

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

TFW_SRV_B_SUSPEND is set in tfw_http_hm_control() after check for TFW_SRV_B_HMONITOR, so TFW_SRV_B_SUSPEND can be set for HM-enabled servers only. test_bit(TFW_SRV_B_HMONITOR, &srv->hm_flags) isn't needed here and can be replaced by WARN_ON_ONCE().

...also see comment for tfw_http_hm_control(). Should the function look like?

unsigned long tmp = READ_ONCE(srv->hm_flags) & (TFW_SRV_B_HMONITOR | TFW_SRV_B_SUSPEND);
return tmp == (TFW_SRV_B_HMONITOR | TFW_SRV_B_SUSPEND));

It this way we can read and check both the flags at onece.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected: #877 (comment).

@@ -64,9 +65,15 @@ typedef struct {
atomic64_t refcnt;
unsigned int weight;
unsigned int flags;
unsigned long hm_flags;
Copy link
Contributor

Choose a reason for hiding this comment

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

I had a look at flags and it's needed for live reconfiguration only, so why not to treat the TFW_CFG_F_* the same way through atomic bit operations? It's really confusing to see two different flags. There is nothing about performance or massive concurrent updates, the only difference is that hm_flags can be ptentially updated concurrently.

Also TFW_SRV_B_HMONITOR is cleared in tfw_server_destroy() -> tfw_apm_del_srv() called from tfw_sock_srv_grace_shutdown_srv(), which sets srv->flags |= TFW_CFG_F_DEL, so probably there is a direct link between TFW_SRV_B_HMONITOR and TFW_CFG_F_DEL flags. @ikoveshnikov could you please also review this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: flags have been merged.


if (!test_bit(TFW_SRV_B_HMONITOR, &srv->hm_flags))
return;

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 don't understand the function intention: we check TFW_SRV_B_HMONITOR at the above and check it again in tfw_srv_suspended() at the calls at the below - is it some kind of protection against concurrent clearing from tfw_apm_hm_disable_srv()? If so then why tfw_apm_hm_disable_srv() can't be called just between test_bit(TFW_SRV_B_HMONITOR, &srv->hm_flags) and test_bit(TFW_SRV_B_SUSPEND, &srv->hm_flags)? Also see comment for tfw_srv_suspended().

What's wrong if at this point of code the server is destroying due to reconfiguration? TFW_SRV_B_SUSPEND seems much easier: it seems the worse can happen is just unnecessary calculation of response body calculation or wrong mark the server as dead (the last one will be quickly fixed by the next HM message). @ikoveshnikov also please review the point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Functions tfw_http_hm_control() and tfw_srv_mark_suspended() have been redesigned in accordance with discussion in chat.

}

bool
tfw_apm_get_hm(const char *name, void **res_hm)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not to return hm? Why do we need the pointer and a boolean return value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

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.

Minor fixes are still required. Please if you have some objections about my comments - ping me in the chat.

@@ -681,7 +694,7 @@ tfw_sock_srv_grace_shutdown_srv(TfwSrvGroup *sg, TfwServer *srv)

tfw_server_get(srv);
__tfw_sg_del_srv(sg, srv, false);
srv->flags |= TFW_CFG_F_DEL;
set_bit(TFW_CFG_B_DEL, &srv->flags);
Copy link
Contributor

Choose a reason for hiding this comment

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

There are several other places which are good to update for the code consistency:

./sock_srv.c:1993:		if (!(srv->flags & TFW_CFG_M_ACTION)) {
./sock_srv.c:2000:		else if (srv->flags & TFW_CFG_F_MOD)
./sock_srv.c:2012:		if (!(srv->flags & TFW_CFG_F_ADD))
./sock_srv.c:2066:		if (!(srv->flags & TFW_CFG_M_ACTION))
./http_sess.c:757:			if (unlikely(srv->flags & TFW_CFG_F_DEL)) {

While technically it's OK to read the flags on x86-64 in this manner, it's better to have to code consistent in using the same API to access the flags.

Copy link
Contributor Author

@aleksostapenko aleksostapenko Feb 19, 2018

Choose a reason for hiding this comment

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

Corrected, but with TFW_CFG_M_ACTION - left the same as didn't find acceptable interface for bit mask checking.

flags | TFW_SRV_F_SUSPEND);
if (likely(old_flags == flags)) {
TFW_WARN_ADDR("server has been suspended: limit"
" for bad responses is exceeded",
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add resp->status to the message to provide more information to a system administrator.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


BUG_ON(!hm);
if (hm->codes && test_bit(HTTP_CODE_BIT_NUM(status), hm->codes))
return true;
Copy link
Contributor

@krizhanovsky krizhanovsky Feb 17, 2018

Choose a reason for hiding this comment

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

Please use crc32 of the first response, but in auto mode only.

However, for applications with dynamic content, it's good to be able to explicitly disable crc32 check. Probably, the easiest way to do this is just explicitly configure auto HM without crc32, e.g.

health_check auto {
	request		"GET / HTTTP/1.0\r\n\r\n";
	request_url	"/";
	resp_code	200;
	timeout		10;
}

Other possibility is to explicitly set auto value for crc32 field if a user want's auto calculated crc32, e.g.:

health_check auto {
	request		"GET / HTTTP/1.0\r\n\r\n";
	request_url	"/";
	resp_code	200;
        resp_crc32      auto;
	timeout		10;
}

Please update the Wiki about explicit auto configuration and automatic crc32 calculation.

if (hm->crc32 && body->len &&
tfw_str_crc32_calc(body) == hm->crc32)
return true;

Copy link
Contributor

Choose a reason for hiding this comment

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

Please print here a warning with expected crc32 (needed for auto mode), the response crc32 and response status code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

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.

Thanks for the corrections! Good to merge.

{
return !tfw_srv_conn_restricted(conn)
&& !tfw_srv_conn_queue_full(conn)
&& tfw_srv_conn_get_if_live(conn);
&& tfw_srv_conn_get_if_live(conn)
&& (hmonitor || !tfw_srv_suspended((TfwServer *)conn->peer));
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 make tfw_srv_suspended() check before any others:

  • That will reduce number of atomic operations.
  • An extra reference might be taken in tfw_srv_conn_get_if_live(). The reference won't be released if tfw_srv_suspended failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

* helth monitoring of backend server.
*/
if (!(((TfwHttpReq *)msg)->flags & TFW_HTTP_HMONITOR)
&& tfw_srv_suspended(srv))
Copy link
Contributor

Choose a reason for hiding this comment

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

The check can be done before rcu operations: no need trying to schedule request if server is suspended.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

@@ -255,6 +272,24 @@ tfw_srv_conn_need_resched(TfwSrvConn *srv_conn)
return ((ACCESS_ONCE(srv_conn->recns) >= sg->max_recns));
}

/*
* Put server into alive or suspended (excluded from processing) state.
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment is misleading: function can't put server into suspended state.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

TfwApmHMStats *hmstats;
atomic64_t rcount;
unsigned long jtmstamp;
struct timer_list timer;
Copy link
Contributor

Choose a reason for hiding this comment

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

Sending requests should be done in separate kernel not in timer function. Must be done as a part of #736

if (!(srv->flags & TFW_CFG_M_ACTION))
continue;

tfw_cfgop_update_srv_health(srv, sg_cfg->hm_name, hm);
Copy link
Contributor

Choose a reason for hiding this comment

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

It was admitted that the main disadvantage of merged ik-51 branch (runtime backend server reconfiguration) was too many traversal across lists. In this patch you traverse the same sg->srv_list twice: here and in tfw_cfgop_update_sg_srv_list(). Can't we do the same job with only one traversal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

TfwSrvGroup *sg = sg_cfg->parsed_sg;
void *hm = NULL;

if (sg_cfg->hm_name && !(hm = tfw_apm_get_hm(sg_cfg->hm_name)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Nobody checks if user specified available health monitor in tfw_cfgop_health_monitor() or later during cfgend(). In that case we may get an error here, tfw_cfgop_start_sg_cfg() will return -EINVAL and so does tfw_sock_srv_start() . This behaviour is not user-friendly since errors on start are non-recoverable and TempestaFW will be stopped. Too hard penalty for possible misprints as for me.

Instead we must check if the health monitor named sg_cfg->hm_name is available on cfgend stage. Error will be found, a new configuration will be dropped, and the TempestaFW will continue working with old good configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected.

* @rearm - flag for gracefull stopping of @timer;
*/
typedef struct {
TfwApmHM *hm;
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems at least hmstats, hm are server group-wide options and should be stored there. We can also use one timer per server group: send health check message to all server in group. What do you think? not the comment below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We cannot make hmstats group-wide - since this will break the essence of the task (server-wide HTTP health statistics and server-wide suspend/alive decisions).
As for hm - yes, theoretically we can use it group-wide, but for now I'd prefer leave it server-wide for three reasons:

  1. Changing current implementation (originally grounded on a server-wide approach) will require significant code changes and re-testing.
  2. In current approach all health monitoring functionality is concentrated in APM module and the there is no need to spreading it between other modules (e.g sock_srv etc.)
  3. Current solution is more flexible and give more granularity in HM management if it will be needed in future, and I do not see significant advantages in group-wide approach except of saving memory on hm pointers for each server.

Copy link
Contributor

@vankoven vankoven 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! I've commented a couple of moments where performance optimisation is possible.

* and still remain in new configuration too.
*/
static void
tfw_cfgop_update_sg_health(TfwCfgSrvGroup *sg_cfg, void *hm)
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 more like a note to all places where tfw_cfgop_update_srv_health() is called. All servers in the group has the same hmonitor. That means you can check that hmonitor has changed only once, like it done here for scheduler:

if (tfw_cfgop_sched_changed(sg_cfg))
sg_cfg->reconf_flags |= TFW_CFG_MDF_SG_SCHED;

We can add a new flag TFW_CFG_MDF_SG_HMON and set it in tfw_cfgop_setup_srv_group().

  • Then when we enter tfw_cfgop_update_sg_health() function, we can return immediately if the flag is not set.
  • Then we update server list in tfw_cfgop_update_sg_srv_list() we need to call tfw_cfgop_update_srv_health() only if the flag set.
  • the tfw_apm_hm_srv_eq() check in tfw_cfgop_update_sg_health() won't be needed.

This suggestion is only performance optimisation. The only effect is reducing of tfw_apm_hm_srv_eq() calls: once per group instead of once per server.

* later (in 'tfw_cfgop_update_sg_srv_list()') it
* will be stopped and removed.
*/
if (!(srv->flags & TFW_CFG_M_ACTION))
Copy link
Contributor

Choose a reason for hiding this comment

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

The check is obsolete: the function is called now only if tfw_cfgop_update_sg_srv_list() won't be called. Thus srv->flags & TFW_CFG_M_ACTION == TFW_CFG_F_KEEP for all server in group.

if (tfw_cfg_check_single_val(ce))
return -EINVAL;
if (!tfw_cfgop_sg_set_hm_name(sg_cfg, ce->vals[0])) {
TFW_ERR_NL("Unable to add group's health"
Copy link
Contributor

Choose a reason for hiding this comment

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

The string can be fit in single line.

)
return response

def make_502_expected():
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 not specific to this test, it's rather generic one. Please move the function to chains.py. Almost the same was done in another PR: https://github.com/tempesta-tech/tempesta/pull/897/files#diff-2395c33b49b27bf69b94b134a3625992R19

)
path = self.tester.get_server_path()
stats, _ = self.tempesta.get_server_stats(path)
s = r'HTTP availability\s+: (\d+)'
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here: too generic steps in the function for the test. I'd create a new helper class in tempesta.py, which can help to pass server statistics. Init it with pointer to Tempesta instance, server group and the server name and use like helpers.tempesta.Stats class. No need to parse everything in server stats in this PR, just only needed stats.

@vladtcvs Do you have any suggestions here?



class TestHealthMonitor(functional.FunctionalTest):
""" Test for health monitor functionality with stress option.
Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't say it's 'regression' test. It's definitely test for a independent new feature - health monitoring. Please move the test to it's own directory. We'll need more tests about HTTP availability in future.

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