MEDIUM: stream: support use-server rules with dynamic names

With server-template was introduced the possibility to scale the
number of servers in a backend without needing a configuration change
and associated reload. On the other hand it became impractical to
write use-server rules for these servers as they would only accept
existing server labels as argument. This patch allows the use of
log-format notation to describe targets of a use-server rules, such
as in the example below:

  listen test
    bind *:1234
    use-server %[hdr(srv)] if { hdr(srv) -m found }
    use-server s1 if { path / }
    server s1 127.0.0.1:18080
    server s2 127.0.0.1:18081

If a use-server rule is applied because it was conditionned by an
ACL returning true, but the target of the use-server rule cannot be
resolved, no other use-server rule is evaluated and we fall back to
load balancing.

This feature was requested on the ML, and bumped with issue #563.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 8eb3f8e..8347e8a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -11141,7 +11141,8 @@
   May be used in sections :   defaults | frontend | listen | backend
                                   no   |    no    |   yes  |   yes
   Arguments :
-    <server>    is the name of a valid server in the same backend section.
+    <server>    is the name of a valid server in the same backend section
+                or a "log-format" string resolving to a server name.
 
     <condition> is a condition composed of ACLs, as described in section 7.
 
@@ -11186,6 +11187,13 @@
      # all the rest is forwarded to this server
      server  default 192.168.0.2:443 check
 
+  When <server> is a simple name, it is checked against existing servers in the
+  configuration and an error is reported if the specified server does not exist.
+  If it is a log-format, no check is performed when parsing the configuration,
+  and if we can't resolve a valid server name at runtime but the use-server rule
+  was conditionned by an ACL returning true, no other use-server rule is applied
+  and we fall back to load balancing.
+
   See also: "use_backend", section 5 about server and section 7 about ACLs.
 
 
diff --git a/include/types/arg.h b/include/types/arg.h
index a9778f2..80e0b0a 100644
--- a/include/types/arg.h
+++ b/include/types/arg.h
@@ -81,6 +81,7 @@
 	ARGC_SRV,      /* server line */
 	ARGC_SPOE,     /* spoe message args */
 	ARGC_UBK,      /* use_backend message */
+	ARGC_USRV,     /* use-server message */
 };
 
 /* flags used when compiling and executing regex */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index a3da428..84ac8d0 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -509,10 +509,14 @@
 struct server_rule {
 	struct list list;			/* list linked to from the proxy */
 	struct acl_cond *cond;			/* acl condition to meet */
+	int dynamic;
 	union {
 		struct server *ptr;		/* target server */
 		char *name;			/* target server name during config parsing */
 	} srv;
+	struct list expr;		/* logformat expression to use for dynamic rules */
+	char *file;
+	int line;
 };
 
 struct persist_rule {
diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c
index 9bffee5..ffc575c 100644
--- a/src/cfgparse-listen.c
+++ b/src/cfgparse-listen.c
@@ -1592,6 +1592,8 @@
 		rule = calloc(1, sizeof(*rule));
 		rule->cond = cond;
 		rule->srv.name = strdup(args[1]);
+		rule->line = linenum;
+		rule->file = strdup(file);
 		LIST_INIT(&rule->list);
 		LIST_ADDQ(&curproxy->server_rules, &rule->list);
 		curproxy->be_req_ana |= AN_REQ_SRV_RULES;
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 2c80083..9b4a0be 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2701,7 +2701,40 @@
 
 		/* find the target server for 'use_server' rules */
 		list_for_each_entry(srule, &curproxy->server_rules, list) {
-			struct server *target = findserver(curproxy, srule->srv.name);
+			struct server *target;
+			struct logformat_node *node;
+			char *server_name;
+
+			/* We try to parse the string as a log format expression. If the result of the parsing
+			 * is only one entry containing a single string, then it's a standard string corresponding
+			 * to a static rule, thus the parsing is cancelled and we fall back to setting srv.ptr.
+			 */
+			server_name = srule->srv.name;
+			LIST_INIT(&srule->expr);
+			curproxy->conf.args.ctx = ARGC_USRV;
+			err = NULL;
+			if (!parse_logformat_string(server_name, curproxy, &srule->expr, 0, SMP_VAL_FE_HRQ_HDR, &err)) {
+				ha_alert("Parsing [%s:%d]; use-server rule failed to parse log-format '%s' : %s.\n",
+						srule->file, srule->line, server_name, err);
+				free(err);
+				cfgerr++;
+				continue;
+			}
+			node = LIST_NEXT(&srule->expr, struct logformat_node *, list);
+
+			if (!LIST_ISEMPTY(&srule->expr)) {
+				if (node->type != LOG_FMT_TEXT || node->list.n != &srule->expr) {
+					srule->dynamic = 1;
+					free(server_name);
+					continue;
+				}
+				free(node->arg);
+				free(node);
+			}
+
+			srule->dynamic = 0;
+			srule->srv.name = server_name;
+			target = findserver(curproxy, srule->srv.name);
 
 			if (!target) {
 				ha_alert("config : %s '%s' : unable to find server '%s' referenced in a 'use-server' rule.\n",
diff --git a/src/stream.c b/src/stream.c
index c059a7a..8b9d885 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -1169,7 +1169,20 @@
 				ret = !ret;
 
 			if (ret) {
-				struct server *srv = rule->srv.ptr;
+				struct server *srv;
+
+				if (rule->dynamic) {
+					struct buffer *tmp = get_trash_chunk();
+
+					if (!build_logline(s, tmp->area, tmp->size, &rule->expr))
+						break;
+
+					srv = findserver(s->be, tmp->area);
+					if (!srv)
+						break;
+				}
+				else
+					srv = rule->srv.ptr;
 
 				if ((srv->cur_state != SRV_ST_STOPPED) ||
 				    (px->options & PR_O_PERSIST) ||