Hello Haproxy ML,

Here are patches attached to this mail to add "server templates" feature to haproxy.

The two first patches consist in moving code to be reused both during 'server' lines parsing and during and server templates initializations.
A new CLI command has also been added (see "init server-templates backend).

Third patch adds server_templates_init() function to be called to initialize server templates attached to a proxy server list.

The remaining patch adds support for 'server-templates' configuration file new keyword and updates the documentation.

Feel free to review/test/comment upon these patches. Any suggestion, are welcome.

Regards,

Fred.
>From 423489c434f9152b26ed3f6bde61bbb1db8cde5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <[email protected]>
Date: Thu, 30 Mar 2017 14:18:30 +0200
Subject: [PATCH 1/4] MINOR: server: Extract the code responsible of copying
 default-server settings.

This patch moves the code responsible of copying default server settings
to a new server instance from parse_server() function to new defsrv_*_cpy()
functions which may be used both during server lines parsing and during server
templates initializations to come.

These defsrv_*_cpy() do not make any reference to anything else than default
server settings.
---
 src/server.c | 325 ++++++++++++++++++++++++++++++++++-------------------------
 1 file changed, 187 insertions(+), 138 deletions(-)

diff --git a/src/server.c b/src/server.c
index 23343d8..897b8c0 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1580,6 +1580,189 @@ static void display_parser_err(const char *file, int linenum, char **args, int c
 		      file, linenum, args[0], args[1], args[cur_arg]);
 }
 
+static void defsrv_conn_src_sport_range_cpy(struct server *srv,
+                                            struct server *defsrv)
+{
+	int range_sz;
+
+	range_sz = defsrv->conn_src.sport_range->size;
+	if (range_sz > 0) {
+		srv->conn_src.sport_range = port_range_alloc_range(range_sz);
+		if (srv->conn_src.sport_range != NULL) {
+			int i;
+
+			for (i = 0; i < range_sz; i++) {
+				srv->conn_src.sport_range->ports[i] =
+					defsrv->conn_src.sport_range->ports[i];
+			}
+		}
+	}
+}
+
+/*
+ * Copy <defsrv> connection source settings to <srv> server object connection
+ * allocating everything needed.
+ */
+static void defsrv_conn_src_cpy(struct server *srv, struct server *defsrv)
+{
+	srv->conn_src.opts = defsrv->conn_src.opts;
+	srv->conn_src.source_addr = defsrv->conn_src.source_addr;
+
+	/* Source port range copy. */
+	if (defsrv->conn_src.sport_range != NULL)
+		defsrv_conn_src_sport_range_cpy(srv, defsrv);
+
+	if (defsrv->conn_src.bind_hdr_name != NULL) {
+		srv->conn_src.bind_hdr_name = strdup(defsrv->conn_src.bind_hdr_name);
+		srv->conn_src.bind_hdr_len = strlen(defsrv->conn_src.bind_hdr_name);
+	}
+	srv->conn_src.bind_hdr_occ = defsrv->conn_src.bind_hdr_occ;
+	srv->conn_src.tproxy_addr  = defsrv->conn_src.tproxy_addr;
+	if (defsrv->conn_src.iface_name != NULL)
+		srv->conn_src.iface_name = strdup(defsrv->conn_src.iface_name);
+}
+
+/*
+ * Copy <defsrv> server SSL settings to <srv> server allocating
+ * everything needed.
+ */
+#if defined(USE_OPENSSL)
+static void defsrv_ssl_settings_cpy(struct server *srv, struct server *defsrv)
+{
+	if (defsrv->ssl_ctx.ca_file != NULL)
+		srv->ssl_ctx.ca_file = strdup(defsrv->ssl_ctx.ca_file);
+	if (defsrv->ssl_ctx.crl_file != NULL)
+		srv->ssl_ctx.crl_file = strdup(defsrv->ssl_ctx.crl_file);
+	if (defsrv->ssl_ctx.client_crt != NULL)
+		srv->ssl_ctx.client_crt = strdup(defsrv->ssl_ctx.client_crt);
+
+	srv->ssl_ctx.verify = defsrv->ssl_ctx.verify;
+
+	if (defsrv->ssl_ctx.verify_host != NULL)
+		srv->ssl_ctx.verify_host = strdup(defsrv->ssl_ctx.verify_host);
+	if (defsrv->ssl_ctx.ciphers != NULL)
+		srv->ssl_ctx.ciphers = strdup(defsrv->ssl_ctx.ciphers);
+	if (defsrv->sni_expr != NULL)
+		srv->sni_expr = strdup(defsrv->sni_expr);
+}
+#endif
+
+/*
+ * Copy <defsrv> server settings to <srv> server allocating
+ * everything needed.
+ */
+static void defsrv_settings_cpy(struct server *srv, struct server *defsrv)
+{
+	/* Connection source settings copy */
+	defsrv_conn_src_cpy(srv, defsrv);
+
+	srv->pp_opts = defsrv->pp_opts;
+	if (defsrv->rdr_pfx != NULL) {
+		srv->rdr_pfx = strdup(defsrv->rdr_pfx);
+		srv->rdr_len = defsrv->rdr_len;
+	}
+	if (defsrv->cookie != NULL) {
+		srv->cookie = strdup(defsrv->cookie);
+		srv->cklen  = defsrv->cklen;
+	}
+	srv->use_ssl                  = defsrv->use_ssl;
+	srv->check.addr = srv->agent.addr = defsrv->check.addr;
+	srv->check.use_ssl            = defsrv->check.use_ssl;
+	srv->check.port               = defsrv->check.port;
+	/* Note: 'flags' field has potentially been already initialized. */
+	srv->flags                   |= defsrv->flags;
+	srv->do_check                 = defsrv->do_check;
+	srv->do_agent                 = defsrv->do_agent;
+	if (srv->check.port)
+		srv->flags |= SRV_F_CHECKPORT;
+	srv->check.inter              = defsrv->check.inter;
+	srv->check.fastinter          = defsrv->check.fastinter;
+	srv->check.downinter          = defsrv->check.downinter;
+	srv->agent.use_ssl            = defsrv->agent.use_ssl;
+	srv->agent.port               = defsrv->agent.port;
+	if (defsrv->agent.send_string != NULL)
+		srv->agent.send_string = strdup(defsrv->agent.send_string);
+	srv->agent.send_string_len    = defsrv->agent.send_string_len;
+	srv->agent.inter              = defsrv->agent.inter;
+	srv->agent.fastinter          = defsrv->agent.fastinter;
+	srv->agent.downinter          = defsrv->agent.downinter;
+	srv->maxqueue                 = defsrv->maxqueue;
+	srv->minconn                  = defsrv->minconn;
+	srv->maxconn                  = defsrv->maxconn;
+	srv->slowstart                = defsrv->slowstart;
+	srv->observe                  = defsrv->observe;
+	srv->onerror                  = defsrv->onerror;
+	srv->onmarkeddown             = defsrv->onmarkeddown;
+	srv->onmarkedup               = defsrv->onmarkedup;
+	if (defsrv->trackit != NULL)
+		srv->trackit = strdup(defsrv->trackit);
+	srv->consecutive_errors_limit = defsrv->consecutive_errors_limit;
+	srv->uweight = srv->iweight   = defsrv->iweight;
+
+	srv->check.send_proxy         = defsrv->check.send_proxy;
+	/* health: up, but will fall down at first failure */
+	srv->check.rise = srv->check.health = defsrv->check.rise;
+	srv->check.fall               = defsrv->check.fall;
+
+	/* Here we check if 'disabled' is the default server state */
+	if (defsrv->admin & (SRV_ADMF_CMAINT | SRV_ADMF_FMAINT)) {
+		srv->admin |= SRV_ADMF_CMAINT | SRV_ADMF_FMAINT;
+		srv->state        = SRV_ST_STOPPED;
+		srv->check.state |= CHK_ST_PAUSED;
+		srv->check.health = 0;
+	}
+
+	/* health: up but will fall down at first failure */
+	srv->agent.rise	= srv->agent.health = defsrv->agent.rise;
+	srv->agent.fall	              = defsrv->agent.fall;
+
+	srv->dns_opts.family_prio = defsrv->dns_opts.family_prio;
+	if (srv->dns_opts.family_prio == AF_UNSPEC)
+		srv->dns_opts.family_prio = AF_INET6;
+	memcpy(srv->dns_opts.pref_net,
+	       defsrv->dns_opts.pref_net,
+	       sizeof srv->dns_opts.pref_net);
+	srv->dns_opts.pref_net_nb     = defsrv->dns_opts.pref_net_nb;
+
+	srv->init_addr_methods        = defsrv->init_addr_methods;
+	srv->init_addr                = defsrv->init_addr;
+#if defined(USE_OPENSSL)
+	defsrv_ssl_settings_cpy(srv, defsrv);
+#endif
+#ifdef TCP_USER_TIMEOUT
+	srv->tcp_ut = defsrv->tcp_ut;
+#endif
+}
+
+static struct server *new_server(struct proxy *proxy)
+{
+	struct server *srv;
+
+	srv = calloc(1, sizeof *srv);
+	if (!srv)
+		return NULL;
+
+	srv->obj_type = OBJ_TYPE_SERVER;
+	srv->proxy = proxy;
+	LIST_INIT(&srv->actconns);
+	LIST_INIT(&srv->pendconns);
+	LIST_INIT(&srv->priv_conns);
+	LIST_INIT(&srv->idle_conns);
+	LIST_INIT(&srv->safe_conns);
+
+	srv->state = SRV_ST_RUNNING; /* early server setup */
+	srv->last_change = now.tv_sec;
+
+	srv->check.status = HCHK_STATUS_INI;
+	srv->check.server = srv;
+	srv->check.tcpcheck_rules = &proxy->tcpcheck_rules;
+
+	srv->agent.status = HCHK_STATUS_INI;
+	srv->agent.server = srv;
+
+	return srv;
+}
+
 int parse_server(const char *file, int linenum, char **args, struct proxy *curproxy, struct proxy *defproxy)
 {
 	struct server *newsrv = NULL;
@@ -1622,7 +1805,8 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 			struct protocol *proto;
 			struct dns_resolution *curr_resolution;
 
-			if ((newsrv = calloc(1, sizeof(*newsrv))) == NULL) {
+			newsrv = new_server(curproxy);
+			if (!newsrv) {
 				Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
 				err_code |= ERR_ALERT | ERR_ABORT;
 				goto out;
@@ -1631,20 +1815,8 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 			/* the servers are linked backwards first */
 			newsrv->next = curproxy->srv;
 			curproxy->srv = newsrv;
-			newsrv->proxy = curproxy;
 			newsrv->conf.file = strdup(file);
 			newsrv->conf.line = linenum;
-
-			newsrv->obj_type = OBJ_TYPE_SERVER;
-			LIST_INIT(&newsrv->actconns);
-			LIST_INIT(&newsrv->pendconns);
-			LIST_INIT(&newsrv->priv_conns);
-			LIST_INIT(&newsrv->idle_conns);
-			LIST_INIT(&newsrv->safe_conns);
-			newsrv->flags = 0;
-			newsrv->admin = 0;
-			newsrv->state = SRV_ST_RUNNING; /* early server setup */
-			newsrv->last_change = now.tv_sec;
 			newsrv->id = strdup(args[1]);
 
 			/* several ways to check the port component :
@@ -1721,131 +1893,8 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 				goto out;
 			}
 
-			/*
-			 * In this section we copy default-server connection source settings to
-			 * the new server object connection source
-			 * (newsrv->conn_src <- curproxy->defsrv.conn_src).
-			 */
-			newsrv->conn_src.opts = curproxy->defsrv.conn_src.opts;
-			newsrv->conn_src.source_addr = curproxy->defsrv.conn_src.source_addr;
-			if (curproxy->defsrv.conn_src.sport_range != NULL) {
-				int i, def_sport_range_sz;
-				struct server *default_srv;
-
-				default_srv = &curproxy->defsrv;
-				def_sport_range_sz = default_srv->conn_src.sport_range->size;
-				if (def_sport_range_sz > 0) {
-					newsrv->conn_src.sport_range = port_range_alloc_range(def_sport_range_sz);
-					if (newsrv->conn_src.sport_range) {
-						for (i = 0; i < def_sport_range_sz; i++)
-							newsrv->conn_src.sport_range->ports[i] = default_srv->conn_src.sport_range->ports[i];
-					}
-				}
-			}
-			if (curproxy->defsrv.conn_src.bind_hdr_name != NULL) {
-				newsrv->conn_src.bind_hdr_name = strdup(curproxy->defsrv.conn_src.bind_hdr_name);
-				newsrv->conn_src.bind_hdr_len = strlen(curproxy->defsrv.conn_src.bind_hdr_name);
-			}
-			newsrv->conn_src.bind_hdr_occ = curproxy->defsrv.conn_src.bind_hdr_occ;
-			newsrv->conn_src.tproxy_addr = curproxy->defsrv.conn_src.tproxy_addr;
-			if (curproxy->defsrv.conn_src.iface_name != NULL)
-				newsrv->conn_src.iface_name = strdup(curproxy->defsrv.conn_src.iface_name);
-
-			newsrv->pp_opts		= curproxy->defsrv.pp_opts;
-			if (curproxy->defsrv.rdr_pfx != NULL) {
-				newsrv->rdr_pfx = strdup(curproxy->defsrv.rdr_pfx);
-				newsrv->rdr_len = curproxy->defsrv.rdr_len;
-			}
-			if (curproxy->defsrv.cookie != NULL) {
-				newsrv->cookie = strdup(curproxy->defsrv.cookie);
-				newsrv->cklen = curproxy->defsrv.cklen;
-			}
-			newsrv->use_ssl		= curproxy->defsrv.use_ssl;
-			newsrv->check.addr = newsrv->agent.addr = curproxy->defsrv.check.addr;
-			newsrv->check.use_ssl	= curproxy->defsrv.check.use_ssl;
-			newsrv->check.port	= curproxy->defsrv.check.port;
-			/* Note: 'flags' field has potentially been already initialized. */
-			newsrv->flags       |= curproxy->defsrv.flags;
-			newsrv->do_check    = curproxy->defsrv.do_check;
-			newsrv->do_agent    = curproxy->defsrv.do_agent;
-			if (newsrv->check.port)
-				newsrv->flags |= SRV_F_CHECKPORT;
-			newsrv->check.inter	= curproxy->defsrv.check.inter;
-			newsrv->check.fastinter	= curproxy->defsrv.check.fastinter;
-			newsrv->check.downinter	= curproxy->defsrv.check.downinter;
-			newsrv->agent.use_ssl	= curproxy->defsrv.agent.use_ssl;
-			newsrv->agent.port	= curproxy->defsrv.agent.port;
-			if (curproxy->defsrv.agent.send_string != NULL)
-				newsrv->agent.send_string = strdup(curproxy->defsrv.agent.send_string);
-			newsrv->agent.send_string_len = curproxy->defsrv.agent.send_string_len;
-			newsrv->agent.inter	= curproxy->defsrv.agent.inter;
-			newsrv->agent.fastinter	= curproxy->defsrv.agent.fastinter;
-			newsrv->agent.downinter	= curproxy->defsrv.agent.downinter;
-			newsrv->maxqueue	= curproxy->defsrv.maxqueue;
-			newsrv->minconn		= curproxy->defsrv.minconn;
-			newsrv->maxconn		= curproxy->defsrv.maxconn;
-			newsrv->slowstart	= curproxy->defsrv.slowstart;
-			newsrv->observe         = curproxy->defsrv.observe;
-			newsrv->onerror		= curproxy->defsrv.onerror;
-			newsrv->onmarkeddown    = curproxy->defsrv.onmarkeddown;
-			newsrv->onmarkedup      = curproxy->defsrv.onmarkedup;
-			if (curproxy->defsrv.trackit != NULL)
-				newsrv->trackit = strdup(curproxy->defsrv.trackit);
-			newsrv->consecutive_errors_limit
-						= curproxy->defsrv.consecutive_errors_limit;
-			newsrv->uweight = newsrv->iweight
-						= curproxy->defsrv.iweight;
-
-			newsrv->check.status	= HCHK_STATUS_INI;
-			newsrv->check.send_proxy = curproxy->defsrv.check.send_proxy;
-			newsrv->check.rise	= curproxy->defsrv.check.rise;
-			newsrv->check.fall	= curproxy->defsrv.check.fall;
-			newsrv->check.health	= newsrv->check.rise;	/* up, but will fall down at first failure */
-			/* Here we check if 'disabled' is the default server state */
-			if (curproxy->defsrv.admin & (SRV_ADMF_CMAINT | SRV_ADMF_FMAINT)) {
-				newsrv->admin |= SRV_ADMF_CMAINT | SRV_ADMF_FMAINT;
-				newsrv->state = SRV_ST_STOPPED;
-				newsrv->check.state |= CHK_ST_PAUSED;
-				newsrv->check.health = 0;
-			}
-			newsrv->check.server	= newsrv;
-			newsrv->check.tcpcheck_rules	= &curproxy->tcpcheck_rules;
-
-			newsrv->agent.status	= HCHK_STATUS_INI;
-			newsrv->agent.rise	= curproxy->defsrv.agent.rise;
-			newsrv->agent.fall	= curproxy->defsrv.agent.fall;
-			newsrv->agent.health	= newsrv->agent.rise;	/* up, but will fall down at first failure */
-			newsrv->agent.server	= newsrv;
-			newsrv->dns_opts.family_prio = curproxy->defsrv.dns_opts.family_prio;
-			if (newsrv->dns_opts.family_prio == AF_UNSPEC)
-				newsrv->dns_opts.family_prio = AF_INET6;
-			memcpy(newsrv->dns_opts.pref_net,
-			       curproxy->defsrv.dns_opts.pref_net,
-			       sizeof(newsrv->dns_opts.pref_net));
-			newsrv->dns_opts.pref_net_nb = curproxy->defsrv.dns_opts.pref_net_nb;
-			newsrv->init_addr_methods = curproxy->defsrv.init_addr_methods;
-			newsrv->init_addr         = curproxy->defsrv.init_addr;
-#if defined(USE_OPENSSL)
-			/* SSL config. */
-			if (curproxy->defsrv.ssl_ctx.ca_file != NULL)
-				newsrv->ssl_ctx.ca_file = strdup(curproxy->defsrv.ssl_ctx.ca_file);
-			if (curproxy->defsrv.ssl_ctx.crl_file != NULL)
-				newsrv->ssl_ctx.crl_file = strdup(curproxy->defsrv.ssl_ctx.crl_file);
-			if (curproxy->defsrv.ssl_ctx.client_crt != NULL)
-				newsrv->ssl_ctx.client_crt = strdup(curproxy->defsrv.ssl_ctx.client_crt);
-			newsrv->ssl_ctx.verify = curproxy->defsrv.ssl_ctx.verify;
-			if (curproxy->defsrv.ssl_ctx.verify_host != NULL)
-				newsrv->ssl_ctx.verify_host = strdup(curproxy->defsrv.ssl_ctx.verify_host);
-			if (curproxy->defsrv.ssl_ctx.ciphers != NULL)
-				newsrv->ssl_ctx.ciphers = strdup(curproxy->defsrv.ssl_ctx.ciphers);
-			if (curproxy->defsrv.sni_expr != NULL)
-				newsrv->sni_expr = strdup(curproxy->defsrv.sni_expr);
-#endif
-
-#ifdef TCP_USER_TIMEOUT
-			newsrv->tcp_ut = curproxy->defsrv.tcp_ut;
-#endif
-
+			/* Copy default server settings to new server settings. */
+			defsrv_settings_cpy(newsrv, &curproxy->defsrv);
 			cur_arg = 3;
 		} else {
 			newsrv = &curproxy->defsrv;
-- 
2.1.4

>From 26c181e0ca119a7eb2d742c849ef01e5330cac9f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <[email protected]>
Date: Thu, 30 Mar 2017 17:32:36 +0200
Subject: [PATCH 2/4] MINOR: server: Extract the code which finalize server
 initializations after 'server' lines parsing.

This patch moves the code which is responsible of finalizing server initializations
after having fully parsed a 'server' line (health-check, agent check and SNI expression
initializations) from parse_server() to new functions.
---
 src/server.c | 341 +++++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 217 insertions(+), 124 deletions(-)

diff --git a/src/server.c b/src/server.c
index 897b8c0..c645c62 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1534,20 +1534,26 @@ const char *server_parse_maxconn_change_request(struct server *sv,
 }
 
 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
-static int server_parse_sni_expr(struct server *newsrv, struct proxy *px, char **err)
+static struct sample_expr *srv_sni_sample_parse_expr(struct server *srv,
+                                                     const char *file, int linenum, char **err)
 {
 	int idx;
-	struct sample_expr *expr;
 	const char *args[] = {
-		newsrv->sni_expr,
+		srv->sni_expr,
 		NULL,
 	};
 
 	idx = 0;
 	proxy->conf.args.ctx = ARGC_SRV;
 
-	expr = sample_parse_expr((char **)args, &idx, px->conf.file, px->conf.line,
-	                         err, &proxy->conf.args);
+	return sample_parse_expr((char **)args, &idx, file, linenum, err, &proxy->conf.args);
+}
+
+static int server_parse_sni_expr(struct server *newsrv, struct proxy *px, char **err)
+{
+	struct sample_expr *expr;
+
+	expr = srv_sni_sample_parse_expr(newsrv, px->conf.file, px->conf.line, err);
 	if (!expr) {
 		memprintf(err, "error detected while parsing sni expression : %s", *err);
 		return ERR_ALERT | ERR_FATAL;
@@ -1557,7 +1563,7 @@ static int server_parse_sni_expr(struct server *newsrv, struct proxy *px, char *
 		memprintf(err, "error detected while parsing sni expression : "
 		          " fetch method '%s' extracts information from '%s', "
 		          "none of which is available here.\n",
-		          args[0], sample_src_names(expr->fetch->use));
+		          newsrv->sni_expr, sample_src_names(expr->fetch->use));
 		return ERR_ALERT | ERR_FATAL;
 	}
 
@@ -1763,6 +1769,207 @@ static struct server *new_server(struct proxy *proxy)
 	return srv;
 }
 
+/*
+ * Validate <srv> server health-check settings.
+ * Returns 0 if everything is OK, -1 if not.
+ */
+static int server_healthcheck_validate(const char *file, int linenum, struct server *srv)
+{
+	struct tcpcheck_rule *r = NULL;
+	struct list *l;
+
+	/*
+	 * We need at least a service port, a check port or the first tcp-check rule must
+	 * be a 'connect' one when checking an IPv4/IPv6 server.
+	 */
+	if ((srv_check_healthcheck_port(&srv->check) != 0) ||
+	    (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
+		return 0;
+
+	r = (struct tcpcheck_rule *)srv->proxy->tcpcheck_rules.n;
+	if (!r) {
+		Alert("parsing [%s:%d] : server %s has neither service port nor check port. "
+			  "Check has been disabled.\n",
+			  file, linenum, srv->id);
+		return -1;
+	}
+
+	/* search the first action (connect / send / expect) in the list */
+	l = &srv->proxy->tcpcheck_rules;
+	list_for_each_entry(r, l, list) {
+		if (r->action != TCPCHK_ACT_COMMENT)
+			break;
+	}
+
+	if ((r->action != TCPCHK_ACT_CONNECT) || !r->port) {
+		Alert("parsing [%s:%d] : server %s has neither service port nor check port "
+			  "nor tcp_check rule 'connect' with port information. Check has been disabled.\n",
+			  file, linenum, srv->id);
+		return -1;
+	}
+
+	/* scan the tcp-check ruleset to ensure a port has been configured */
+	l = &srv->proxy->tcpcheck_rules;
+	list_for_each_entry(r, l, list) {
+		if ((r->action == TCPCHK_ACT_CONNECT) && (!r->port)) {
+			Alert("parsing [%s:%d] : server %s has neither service port nor check port, "
+				  "and a tcp_check rule 'connect' with no port information. Check has been disabled.\n",
+				  file, linenum, srv->id);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Initialize <srv> health-check structure.
+ * Returns the error string in case of memory allocation failure, NULL if not.
+ */
+static const char *do_health_check_init(struct server *srv, int check_type)
+{
+	const char *ret;
+
+	if (!srv->do_check)
+		return NULL;
+
+	ret = init_check(&srv->check, check_type);
+	if (ret)
+		return ret;
+
+	if (srv->resolution)
+		srv->resolution->opts = &srv->dns_opts;
+
+	srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
+	global.maxsock++;
+
+	return NULL;
+}
+
+static int server_health_check_init(const char *file, int linenum,
+                                    struct server *srv, struct proxy *curproxy)
+{
+	const char *ret;
+
+	if (!srv->do_check)
+		return 0;
+
+	if (srv->trackit) {
+		Alert("parsing [%s:%d]: unable to enable checks and tracking at the same time!\n",
+			file, linenum);
+		return ERR_ALERT | ERR_FATAL;
+	}
+
+	if (server_healthcheck_validate(file, linenum, srv) < 0)
+		return ERR_ALERT | ERR_ABORT;
+
+	/* note: check type will be set during the config review phase */
+	ret = do_health_check_init(srv, 0);
+	if (ret) {
+		Alert("parsing [%s:%d] : %s.\n", file, linenum, ret);
+		return ERR_ALERT | ERR_ABORT;
+	}
+
+	return 0;
+}
+
+/*
+ * Initialize <srv> agent check structure.
+ * Returns the error string in case of memory allocation failure, NULL if not.
+ */
+static const char *do_server_agent_check_init(struct server *srv)
+{
+	const char *ret;
+
+	if (!srv->do_agent)
+		return NULL;
+
+	ret = init_check(&srv->agent, PR_O2_LB_AGENT_CHK);
+	if (ret)
+		return ret;
+
+	if (!srv->agent.inter)
+		srv->agent.inter = srv->check.inter;
+
+	srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
+	global.maxsock++;
+
+	return NULL;
+}
+
+static int server_agent_check_init(const char *file, int linenum,
+                                   struct server *srv, struct proxy *curproxy)
+{
+	const char *ret;
+
+	if (!srv->do_agent)
+		return 0;
+
+	if (!srv->agent.port) {
+		Alert("parsing [%s:%d] : server %s does not have agent port. Agent check has been disabled.\n",
+			  file, linenum, srv->id);
+		return ERR_ALERT | ERR_FATAL;
+	}
+
+	ret = do_server_agent_check_init(srv);
+	if (ret) {
+		Alert("parsing [%s:%d] : %s.\n", file, linenum, ret);
+		return ERR_ALERT | ERR_ABORT;
+	}
+
+	return 0;
+}
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+static int server_sni_expr_init(const char *file, int linenum, char **args, int cur_arg,
+                                struct server *srv, struct proxy *proxy)
+{
+	int ret;
+	char *err = NULL;
+
+	if (!srv->sni_expr)
+		return 0;
+
+	ret = server_parse_sni_expr(srv, proxy, &err);
+	if (!ret)
+	    return 0;
+
+	display_parser_err(file, linenum, args, cur_arg, &err);
+	free(err);
+
+	return ret;
+}
+#endif
+
+/*
+ * Server initializations finalization.
+ * Initialize health check, agent check and SNI expression if enabled.
+ * Must not be called for a default server instance.
+ */
+static int server_finalize_init(const char *file, int linenum, char **args, int cur_arg,
+                                struct server *srv, struct proxy *px)
+{
+	int ret;
+
+	if ((ret = server_health_check_init(file, linenum, srv, px)) != 0 ||
+	    (ret = server_agent_check_init(file, linenum, srv, px)) != 0) {
+		return ret;
+	}
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+	if ((ret = server_sni_expr_init(file, linenum, args, cur_arg, srv, px)) != 0)
+		return ret;
+#endif
+
+	if (srv->flags & SRV_F_BACKUP)
+		px->srv_bck++;
+	else
+		px->srv_act++;
+	srv_lb_commit_status(srv);
+
+	return 0;
+}
+
 int parse_server(const char *file, int linenum, char **args, struct proxy *curproxy, struct proxy *defproxy)
 {
 	struct server *newsrv = NULL;
@@ -2320,124 +2527,10 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 			}
 		}
 
-		/* This check is done only for 'server' instances. */
-		if (!defsrv && newsrv->do_check) {
-			const char *ret;
-
-			if (newsrv->trackit) {
-				Alert("parsing [%s:%d]: unable to enable checks and tracking at the same time!\n",
-					file, linenum);
-				err_code |= ERR_ALERT | ERR_FATAL;
-				goto out;
-			}
-
-			/*
-			 * We need at least a service port, a check port or the first tcp-check rule must
-			 * be a 'connect' one when checking an IPv4/IPv6 server.
-			 */
-			if ((srv_check_healthcheck_port(&newsrv->check) == 0) &&
-			    (is_inet_addr(&newsrv->check.addr) ||
-			     (!is_addr(&newsrv->check.addr) && is_inet_addr(&newsrv->addr)))) {
-				struct tcpcheck_rule *r = NULL;
-				struct list *l;
-
-				r = (struct tcpcheck_rule *)newsrv->proxy->tcpcheck_rules.n;
-				if (!r) {
-					Alert("parsing [%s:%d] : server %s has neither service port nor check port. Check has been disabled.\n",
-					      file, linenum, newsrv->id);
-					err_code |= ERR_ALERT | ERR_FATAL;
-					goto out;
-				}
-				/* search the first action (connect / send / expect) in the list */
-				l = &newsrv->proxy->tcpcheck_rules;
-				list_for_each_entry(r, l, list) {
-					if (r->action != TCPCHK_ACT_COMMENT)
-						break;
-				}
-				if ((r->action != TCPCHK_ACT_CONNECT) || !r->port) {
-					Alert("parsing [%s:%d] : server %s has neither service port nor check port nor tcp_check rule 'connect' with port information. Check has been disabled.\n",
-					      file, linenum, newsrv->id);
-					err_code |= ERR_ALERT | ERR_FATAL;
-					goto out;
-				}
-				else {
-					/* scan the tcp-check ruleset to ensure a port has been configured */
-					l = &newsrv->proxy->tcpcheck_rules;
-					list_for_each_entry(r, l, list) {
-						if ((r->action == TCPCHK_ACT_CONNECT) && (!r->port)) {
-							Alert("parsing [%s:%d] : server %s has neither service port nor check port, and a tcp_check rule 'connect' with no port information. Check has been disabled.\n",
-							      file, linenum, newsrv->id);
-							err_code |= ERR_ALERT | ERR_FATAL;
-							goto out;
-						}
-					}
-				}
-			}
-
-			/* note: check type will be set during the config review phase */
-			ret = init_check(&newsrv->check, 0);
-			if (ret) {
-				Alert("parsing [%s:%d] : %s.\n", file, linenum, ret);
-				err_code |= ERR_ALERT | ERR_ABORT;
-				goto out;
-			}
-
-			if (newsrv->resolution)
-				newsrv->resolution->opts = &newsrv->dns_opts;
-
-			newsrv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
-			global.maxsock++;
-		}
-
-		if (!defsrv && newsrv->do_agent) {
-			const char *ret;
-
-			if (!newsrv->agent.port) {
-				Alert("parsing [%s:%d] : server %s does not have agent port. Agent check has been disabled.\n",
-				      file, linenum, newsrv->id);
-				err_code |= ERR_ALERT | ERR_FATAL;
-				goto out;
-			}
-
-			if (!newsrv->agent.inter)
-				newsrv->agent.inter = newsrv->check.inter;
-
-			ret = init_check(&newsrv->agent, PR_O2_LB_AGENT_CHK);
-			if (ret) {
-				Alert("parsing [%s:%d] : %s.\n", file, linenum, ret);
-				err_code |= ERR_ALERT | ERR_ABORT;
-				goto out;
-			}
-
-			newsrv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
-			global.maxsock++;
-		}
-
-		if (!defsrv) {
-			if (newsrv->flags & SRV_F_BACKUP)
-				curproxy->srv_bck++;
-			else
-				curproxy->srv_act++;
-
-			srv_lb_commit_status(newsrv);
-		}
-#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
-		if (!defsrv && newsrv->sni_expr) {
-			int code;
-			char *err;
-
-			err = NULL;
-
-			code = server_parse_sni_expr(newsrv, curproxy, &err);
-			err_code |= code;
-			if (code) {
-				display_parser_err(file, linenum, args, cur_arg, &err);
-				free(err);
-				if (code & ERR_FATAL)
-					goto out;
-			}
-		}
-#endif
+		if (!defsrv)
+			err_code |= server_finalize_init(file, linenum, args, cur_arg, newsrv, curproxy);
+		if (err_code & ERR_FATAL)
+			goto out;
 	}
 	free(fqdn);
 	return 0;
-- 
2.1.4

>From 1f7bfeb38c0c731b93d4872f882450ba44a74405 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <[email protected]>
Date: Thu, 6 Apr 2017 16:38:14 +0200
Subject: [PATCH 3/4] MINOR: server: Add a function to initialize server
 templates.

Add a new server_templates_init() function to be used to initialize server templates.
These server templates have the same settings as default server.
Also add a new CLI command to call this function as follows:
  "init server-templates backend <backend ID> <nb of server templates>".
---
 include/proto/server.h |  1 +
 include/types/proxy.h  |  4 +++
 include/types/server.h |  2 ++
 src/proxy.c            | 33 ++++++++++++++++++
 src/server.c           | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 131 insertions(+)

diff --git a/include/proto/server.h b/include/proto/server.h
index 6e3ccf3..87694d6 100644
--- a/include/proto/server.h
+++ b/include/proto/server.h
@@ -40,6 +40,7 @@ int srv_downtime(const struct server *s);
 int srv_lastsession(const struct server *s);
 int srv_getinter(const struct check *check);
 int parse_server(const char *file, int linenum, char **args, struct proxy *curproxy, struct proxy *defproxy);
+int server_templates_init(struct proxy *px, int nb);
 int update_server_addr(struct server *s, void *ip, int ip_sin_family, const char *updater);
 const char *update_server_addr_port(struct server *s, const char *addr, const char *port, char *updater);
 struct server *server_find_by_id(struct proxy *bk, int id);
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 5306a3b..12b36c7 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -277,6 +277,10 @@ struct proxy {
 		struct list inspect_rules;      /* inspection rules */
 	} tcp_rep;
 	struct server *srv, defsrv;		/* known servers; default server configuration */
+	struct {                                /* Server templates */
+		int nb;                         /* The number of servers allocated for this templates */
+		struct list list;               /* The list of server templates */
+	} srv_templates;
 	int srv_act, srv_bck;			/* # of servers eligible for LB (UP|!checked) AND (enabled+weight!=0) */
 	int served;				/* # of active sessions currently being served */
 	struct lbprm lbprm;			/* load-balancing parameters */
diff --git a/include/types/server.h b/include/types/server.h
index bfaa941..19e535a 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -274,6 +274,8 @@ struct server {
 		int line;			/* line where the section appears */
 		struct eb32_node id;		/* place in the tree of used IDs */
 	} conf;					/* config information */
+	/* List entry point */
+	struct list list;
 };
 
 /* Descriptor for a "server" keyword. The ->parse() function returns 0 in case of
diff --git a/src/proxy.c b/src/proxy.c
index d158fac..1f9637f 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -742,6 +742,7 @@ void init_new_proxy(struct proxy *p)
 	LIST_INIT(&p->tcp_rep.inspect_rules);
 	LIST_INIT(&p->tcp_req.l4_rules);
 	LIST_INIT(&p->tcp_req.l5_rules);
+	LIST_INIT(&p->srv_templates.list);
 	LIST_INIT(&p->req_add);
 	LIST_INIT(&p->rsp_add);
 	LIST_INIT(&p->listener_queue);
@@ -1719,10 +1720,42 @@ static int cli_parse_enable_frontend(char **args, struct appctx *appctx, void *p
 	return 1;
 }
 
+static int cli_parse_init_server_templates(char **args, struct appctx *appctx, void *private)
+{
+	int nb;
+	struct proxy *px;
+
+	if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+		return 1;
+
+	px = cli_find_backend(appctx, args[3]);
+	if (!px)
+		return 1;
+
+	if (!*args[4]) {
+		appctx->ctx.cli.msg = "A number of server templates to instantiate "
+		                      "is expected.\n";
+		appctx->st0 = CLI_ST_PRINT;
+		return 1;
+	}
+
+	nb = atoi(args[4]);
+	if (nb < 0) {
+		appctx->ctx.cli.msg = "Wrong number of server templates to instantiate.\n";
+		appctx->st0 = CLI_ST_PRINT;
+		return 1;
+	}
+
+	server_templates_init(px, nb);
+
+	return 1;
+}
+
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
 	{ { "disable", "frontend",  NULL }, "disable frontend : temporarily disable specific frontend", cli_parse_disable_frontend, NULL, NULL },
 	{ { "enable", "frontend",  NULL }, "enable frontend : re-enable specific frontend", cli_parse_enable_frontend, NULL, NULL },
+	{ { "init", "server-templates", "backend", NULL }, "init server-templates backend [id]: init server templates (for backend <id>)", cli_parse_init_server_templates, NULL },
 	{ { "set", "maxconn", "frontend",  NULL }, "set maxconn frontend : change a frontend's maxconn setting", cli_parse_set_maxconn_frontend, NULL },
 	{ { "show","servers", "state",  NULL }, "show servers state [id]: dump volatile server information (for backend <id>)", cli_parse_show_servers, cli_io_handler_servers_state },
 	{ { "show", "backend", NULL }, "show backend   : list backends in the current running config", NULL, cli_io_handler_show_backend },
diff --git a/src/server.c b/src/server.c
index c645c62..f76a218 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1970,6 +1970,97 @@ static int server_finalize_init(const char *file, int linenum, char **args, int
 	return 0;
 }
 
+/*
+ * Allocate <nb> server templates for <px> proxy.
+ * This function may be called only after having parsed any configuration file.
+ * Returns 0 if everything has been successfully allocated, -1 if not.
+ * In every cases, the number of server templates successfully allocated (<= n)
+ * is stored in <px> structure.
+ */
+int server_templates_init(struct proxy *px, int nb)
+{
+	int i;
+	struct server *srv, *last_srv;
+	struct sample_expr *expr;
+
+	last_srv = NULL;
+
+	for (i = 0; i < nb; i++) {
+		int next_id;
+
+		expr = NULL;
+		srv = new_server(px);
+		if (!srv)
+			goto err;
+
+		/* Default server settings copy */
+		defsrv_settings_cpy(srv, &px->defsrv);
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+		if (srv->sni_expr) {
+			expr = srv_sni_sample_parse_expr(srv, NULL, 0, NULL);
+			if (!expr || !(expr->fetch->val & SMP_VAL_BE_SRV_CON))
+				goto err;
+
+			px->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY);
+			srv->ssl_ctx.sni = expr;
+		}
+#endif
+
+		/*
+		 * Server check type depends on proxy options2.
+		 */
+		if (do_health_check_init(srv, px->options2 & PR_O2_CHK_ANY) != NULL ||
+		    do_server_agent_check_init(srv) != NULL)
+			goto err;
+
+		/* From here, all memory allocations succeeded. */
+		LIST_ADDQ(&px->srv_templates.list, &srv->list);
+
+		/* Set this new server ID */
+		next_id = 1;
+		next_id = get_next_id(&px->conf.used_server_id, next_id);
+		srv->conf.id.key = srv->puid = next_id;
+		eb32_insert(&px->conf.used_server_id, &srv->conf.id);
+
+		/* Link this new server to its proxy server list. */
+		if (!last_srv) {
+			if (!px->srv) {
+				last_srv = px->srv = srv;
+			}
+			else {
+				last_srv = px->srv;
+				while (last_srv->next)
+					last_srv = last_srv->next;
+				last_srv = last_srv->next = srv;
+			}
+		}
+		else {
+			last_srv = last_srv->next = srv;
+		}
+	}
+	px->srv_templates.nb += i;
+
+	return 0;
+
+ err:
+	/*
+	 * Even if something wrong happened, accumulate the number of
+	 * server templates succeefully allocated.
+	 */
+	px->srv_templates.nb += i;
+	if (srv) {
+		free_check(&srv->check);
+		free_check(&srv->agent);
+	}
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+	if (srv->sni_expr)
+		release_sample_expr(expr);
+#endif
+	free(srv);
+	return -1;
+}
+
 int parse_server(const char *file, int linenum, char **args, struct proxy *curproxy, struct proxy *defproxy)
 {
 	struct server *newsrv = NULL;
-- 
2.1.4

>From fb75d64f40d39deda4a25378c09b93233787103c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <[email protected]>
Date: Fri, 7 Apr 2017 13:47:27 +0200
Subject: [PATCH 4/4] MINOR: cfgparse: Add 'server-templates' keyword to
 initialize server templates.

This patch adds 'server-templates' configuration file keyword supported by any
type of backend to initialize server templates from backend sections.
It is supported by 'defaults' section.
The server templates initializations are done in check_config_validy()
just after having reversed the proxy server lists.
Update the documentation.
---
 doc/configuration.txt | 20 ++++++++++++++++++++
 include/types/proxy.h |  1 +
 src/cfgparse.c        | 29 +++++++++++++++++++++++++++++
 3 files changed, 50 insertions(+)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 81b641e..5d17cee 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1962,6 +1962,7 @@ rspirep                                   -          X         X         X
 rsprep                                    -          X         X         X
 server                                    -          -         X         X
 server-state-file-name                    X          -         X         X
+server-templates                          X          -         X         X
 source                                    X          -         X         X
 srvtimeout                  (deprecated)  X          -         X         X
 stats admin                               -          X         X         X
@@ -7514,6 +7515,25 @@ server-state-file-name [<file>]
   See also: "server-state-file-base", "load-server-state-from-file", and
   "show servers state"
 
+server-templates <nb>
+  Initialize <nb> server templates.
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    no    |   yes  |   yes
+
+  Server templates are servers with settings copied from default server settings.
+  Their names and addresses are not set.
+
+  Example: the following configuration would initialize 5 server templates for
+           'bk' backend with only 'check' as enabled setting and 6 server templates
+           for 'lt' backend with only 'backup' as enabled setting.
+    defaults
+      server-templates 5
+    backend bk
+      default-server check
+    listen lt
+      default-server backup
+      server-templates 6
+
 source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | client | clientip } ]
 source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | hdr_ip(<hdr>[,<occ>]) } ]
 source <addr>[:<port>] [interface <name>]
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 12b36c7..648c746 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -441,6 +441,7 @@ struct proxy {
 						 * this backend. If not specified or void, then the backend
 						 * name is used
 						 */
+	int server_templates;           /* The number of server_templates */
 	struct list filter_configs;		/* list of the filters that are declared on this proxy */
 };
 
diff --git a/src/cfgparse.c b/src/cfgparse.c
index e1b6b3e..4361eeb 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2665,6 +2665,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
 			curproxy->conn_src.tproxy_addr = defproxy.conn_src.tproxy_addr;
 #endif
 			curproxy->load_server_state_from_file = defproxy.load_server_state_from_file;
+			curproxy->server_templates = defproxy.server_templates;
 		}
 
 		if (curproxy->cap & PR_CAP_FE) {
@@ -3177,6 +3178,24 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
 		free(curproxy->dyncookie_key);
 		curproxy->dyncookie_key = strdup(args[1]);
 	}
+	else if (!strcmp(args[0], "server-templates")) { /* Server templates */
+		if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
+			err_code |= ERR_WARN;
+
+		if (*(args[1]) == 0) {
+			Alert("parsing [%s:%d] : '%s' expects <nb> as argument.\n",
+					file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+		curproxy->server_templates = atoi(args[1]);
+		if (curproxy->server_templates < 0) {
+			Alert("parsing [%s:%d] : '%s' expects a positive numeric value\n",
+				  file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+	}
 	else if (!strcmp(args[0], "cookie")) {  /* cookie name */
 		int cur_arg;
 
@@ -8315,6 +8334,16 @@ out_uri_auth_compat:
 			curproxy->srv = next;
 		}
 
+		/* Server templates initialization. */
+		if (curproxy->server_templates > 0 &&
+		    server_templates_init(curproxy, curproxy->server_templates) < 0) {
+			Warning("config : %s '%s', server templates : some allocations failed "
+			        "(asked: %d, succeeded: %d)\n",
+			        proxy_type_str(curproxy), curproxy->id,
+			        curproxy->server_templates, curproxy->srv_templates.nb);
+			err_code |= ERR_WARN;
+		}
+
 		/* Check that no server name conflicts. This causes trouble in the stats.
 		 * We only emit a warning for the first conflict affecting each server,
 		 * in order to avoid combinatory explosion if all servers have the same
-- 
2.1.4

Reply via email to