MINOR: server: implement delete server cli command

Implement a new CLI command 'del server'. It can be used to removed a
dynamically added server. Only servers in maintenance mode can be
removed, and without pending/active/idle connection on it.

Add a new reg-test for this feature. The scenario of the reg-test need
to first add a dynamic server. It is then deleted and a client is used
to ensure that the server is non joinable.

The management doc is updated with the new command 'del server'.
diff --git a/src/server.c b/src/server.c
index e8cbdee..a6c67a0 100644
--- a/src/server.c
+++ b/src/server.c
@@ -4468,6 +4468,131 @@
 	return 1;
 }
 
+/* Parse a "del server" command
+ * Returns 0 if the server has been successfully initialized, 1 on failure.
+ */
+static int cli_parse_delete_server(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	struct proxy *be;
+	struct server *srv;
+	char *be_name, *sv_name;
+
+	if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+		return 1;
+
+	++args;
+
+	sv_name = be_name = args[1];
+	/* split backend/server arg */
+	while (*sv_name && *(++sv_name)) {
+		if (*sv_name == '/') {
+			*sv_name = '\0';
+			++sv_name;
+			break;
+		}
+	}
+
+	if (!*sv_name)
+		return cli_err(appctx, "Require 'backend/server'.");
+
+	/* The proxy servers list is currently not protected by a lock so this
+	 * requires thread isolation.
+	 */
+
+	/* WARNING there is maybe a potential violation of the thread isolation
+	 * mechanism by the pool allocator. The allocator marks the thread as
+	 * harmless before the allocation, but a processing outside of it could
+	 * relies on a particular server triggered at the same time by a
+	 * 'delete server'. Currently, it is unknown if such case is present in
+	 * the current code. If it happens to be, the thread isolation
+	 * mechanism should be improved, maybe with a differentiation between
+	 * read and read+write safe sections.
+	 */
+	thread_isolate();
+
+	get_backend_server(be_name, sv_name, &be, &srv);
+	if (!be) {
+		cli_err(appctx, "No such backend.");
+		goto out;
+	}
+
+	if (!srv) {
+		cli_err(appctx, "No such server.");
+		goto out;
+	}
+
+	if (!(srv->flags & SRV_F_DYNAMIC)) {
+		cli_err(appctx, "Only servers added at runtime via <add server> CLI cmd can be deleted.");
+		goto out;
+	}
+
+	/* Only servers in maintenance can be deleted. This ensures that the
+	 * server is not present anymore in the lb structures (through
+	 * lbprm.set_server_status_down).
+	 */
+	if (!(srv->cur_admin & SRV_ADMF_MAINT)) {
+		cli_err(appctx, "Only servers in maintenance mode can be deleted.");
+		goto out;
+	}
+
+	/* Ensure that there is no active/idle/pending connection on the server.
+	 *
+	 * TODO idle connections should not prevent server deletion. A proper
+	 * cleanup function should be implemented to be used here.
+	 */
+	if (srv->cur_sess || srv->curr_idle_conns ||
+	    !eb_is_empty(&srv->pendconns)) {
+		cli_err(appctx, "Server still has connections attached to it, cannot remove it.");
+		goto out;
+	}
+
+	/* TODO remove server for check list once 'check' will be implemented for
+	 * dynamic servers
+	 */
+
+	/* detach the server from the proxy linked list
+	 * The proxy servers list is currently not protected by a lock, so this
+	 * requires thread_isolate/release.
+	 */
+
+	/* be->srv cannot be empty since we have already found the server with
+	 * get_backend_server */
+	BUG_ON(!be->srv);
+	if (be->srv == srv) {
+		be->srv = srv->next;
+	}
+	else {
+		struct server *next;
+		for (next = be->srv; next && srv != next->next; next = next->next)
+			;
+
+		/* srv cannot be not found since we have already found it
+		 * with get_backend_server */
+		BUG_ON(!next);
+		next->next = srv->next;
+	}
+
+	/* remove srv from addr_node tree */
+	ebpt_delete(&srv->addr_node);
+
+	/* remove srv from idle_node tree for idle conn cleanup */
+	eb32_delete(&srv->idle_node);
+
+	thread_release();
+
+	ha_notice("Server %s/%s deleted.\n", be->id, srv->id);
+	free_server(srv);
+
+	cli_msg(appctx, LOG_INFO, "Server deleted.");
+
+	return 0;
+
+out:
+	thread_release();
+
+	return 1;
+}
+
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
 	{ { "disable", "agent",  NULL }, "disable agent  : disable agent checks (use 'set server' instead)", cli_parse_disable_agent, NULL },
@@ -4481,6 +4606,7 @@
 	{ { "get", "weight", NULL }, "get weight     : report a server's current weight",  cli_parse_get_weight },
 	{ { "set", "weight", NULL }, "set weight     : change a server's weight (deprecated)",  cli_parse_set_weight },
 	{ { "add", "server", NULL }, "add server     : create a new server (EXPERIMENTAL)", cli_parse_add_server, NULL, NULL, NULL, ACCESS_EXPERIMENTAL },
+	{ { "del", "server", NULL }, "del server     : remove a dynamically added server (EXPERIMENTAL)", cli_parse_delete_server, NULL, NULL, NULL, ACCESS_EXPERIMENTAL },
 
 	{{},}
 }};