Introduce support for libosmo-mgcp-client MGW pooling

Large RAN installations may benefit from distributing the RTP voice
stream load over multiple media gateways.

libosmo-mgcp-client supports MGW pooling since version 1.8.0 (more than
one year ago). OsmoBSC has already been making use of it since then (see
osmo-bsc.git 8d22e6870637ed6d392a8a77aeaebc51b23a8a50); lets use this
feature in osmo-msc too.

This commit is also part of a series of patches cleaning up
libosmo-mgcp-client and slowly getting rid of the old non-mgw-pooled VTY
configuration, in order to keep only 1 way to configure
libosmo-mgcp-client through VTY.

Related: SYS#5091
Related: SYS#5987
Change-Id: I7670ba56fe989706579224a364595fdd4b4708ff
diff --git a/src/libmsc/call_leg.c b/src/libmsc/call_leg.c
index e890f75..f8a75c4 100644
--- a/src/libmsc/call_leg.c
+++ b/src/libmsc/call_leg.c
@@ -122,7 +122,13 @@
 
 static void call_leg_mgw_endpoint_gone(struct call_leg *cl)
 {
+	struct mgcp_client *mgcp_client;
 	int i;
+
+	/* Put MGCP client back into MGW pool */
+	mgcp_client = osmo_mgcpc_ep_client(cl->mgw_endpoint);
+	mgcp_client_pool_put(mgcp_client);
+
 	cl->mgw_endpoint = NULL;
 	for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) {
 		if (!cl->rtp[i])
@@ -275,10 +281,17 @@
 	if (cl->rtp[dir])
 		return 0;
 
-	if (!cl->mgw_endpoint)
+	if (!cl->mgw_endpoint) {
+		struct mgcp_client *mgcp_client = mgcp_client_pool_get(gsmnet->mgw.mgw_pool);
+		if (!mgcp_client) {
+			LOG_CALL_LEG(cl, LOGL_ERROR,
+				     "cannot ensure MGW endpoint -- no MGW configured, check configuration!\n");
+			return -ENODEV;
+		}
 		cl->mgw_endpoint = osmo_mgcpc_ep_alloc(cl->fi, CALL_LEG_EV_MGW_ENDPOINT_GONE,
-						       gsmnet->mgw.client, gsmnet->mgw.tdefs, cl->fi->id,
-						       "%s", mgcp_client_rtpbridge_wildcard(gsmnet->mgw.client));
+						       mgcp_client, gsmnet->mgw.tdefs, cl->fi->id,
+						       "%s", mgcp_client_rtpbridge_wildcard(mgcp_client));
+	}
 	if (!cl->mgw_endpoint) {
 		LOG_CALL_LEG(cl, LOGL_ERROR, "failed to setup MGW endpoint\n");
 		return -EIO;
diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c
index be05a95..357b975 100644
--- a/src/libmsc/msc_vty.c
+++ b/src/libmsc/msc_vty.c
@@ -2033,6 +2033,8 @@
 	install_element(GSMNET_NODE, &cfg_net_no_per_loc_upd_cmd);
 	install_element(GSMNET_NODE, &cfg_net_call_wait_cmd);
 	install_element(GSMNET_NODE, &cfg_net_no_call_wait_cmd);
+	mgcp_client_pool_vty_init(GSMNET_NODE, MGW_NODE, " ", msc_network->mgw.mgw_pool);
+
 
 	install_element(CONFIG_NODE, &cfg_msc_cmd);
 	install_node(&msc_node, config_write_msc);
@@ -2066,7 +2068,9 @@
 	/* Timer configuration commands (generic osmo_tdef API) */
 	osmo_tdef_vty_groups_init(MSC_NODE, msc_tdef_group);
 
+	/* Deprecated: Old MGCP config without pooling support in MSC node: */
 	mgcp_client_vty_init(msc_network, MSC_NODE, &msc_network->mgw.conf);
+
 #ifdef BUILD_IU
 	ranap_iu_vty_init(MSC_NODE, (enum ranap_nsap_addr_enc*)&msc_network->iu.rab_assign_addr_enc);
 #endif
diff --git a/src/osmo-msc/msc_main.c b/src/osmo-msc/msc_main.c
index 9cc4120..c6514c2 100644
--- a/src/osmo-msc/msc_main.c
+++ b/src/osmo-msc/msc_main.c
@@ -256,6 +256,7 @@
 						  MSC_HLR_REMOTE_IP_DEFAULT);
 	net->gsup_server_port = MSC_HLR_REMOTE_PORT_DEFAULT;
 
+	net->mgw.mgw_pool = mgcp_client_pool_alloc(net);
 	mgcp_client_conf_init(&net->mgw.conf);
 	net->call_waiting = true;
 	net->lcls_permitted = false;
@@ -546,6 +547,41 @@
 extern void *tall_call_ctx;
 extern void *tall_trans_ctx;
 
+static int msc_mgw_setup(void)
+{
+	struct mgcp_client *mgcp_client_single;
+	unsigned int pool_members_initalized;
+
+	/* Initialize MGW pool. This initalizes and connects all MGCP clients that are currently configured in
+	 * the pool. Adding additional MGCP clients to the pool is possible but the user has to configure and
+	 * (re)connect them manually from the VTY. */
+	pool_members_initalized = mgcp_client_pool_connect(msc_network->mgw.mgw_pool);
+	if (pool_members_initalized) {
+		LOGP(DMSC, LOGL_NOTICE,
+		     "MGW pool with %u pool members configured, (ignoring MGW configuration in VTY node 'msc').\n",
+		     pool_members_initalized);
+		return 0;
+	}
+
+	/* Initialize and connect a single MGCP client. This MGCP client will appear as the one and only pool
+	 * member if there is no MGW pool configured. */
+	LOGP(DMSC, LOGL_NOTICE, "No MGW pool configured, using MGW configuration in VTY node 'msc'\n");
+	mgcp_client_single = mgcp_client_init(msc_network, &msc_network->mgw.conf);
+	if (!mgcp_client_single) {
+		LOGP(DMSC, LOGL_ERROR, "MGW (single) client initalization failed\n");
+		return -EINVAL;
+	}
+	if (mgcp_client_connect(mgcp_client_single)) {
+		LOGP(DMSC, LOGL_ERROR, "MGW (single) connect failed at (%s:%u)\n",
+		     msc_network->mgw.conf.remote_addr,
+		     msc_network->mgw.conf.remote_port);
+		return -EINVAL;
+	}
+	mgcp_client_pool_register_single(msc_network->mgw.mgw_pool, mgcp_client_single);
+
+	return 0;
+}
+
 int main(int argc, char **argv)
 {
 	int rc;
@@ -705,13 +741,8 @@
 	if (sms_queue_start(msc_network) != 0)
 		return -1;
 
-	msc_network->mgw.client = mgcp_client_init(
-			msc_network, &msc_network->mgw.conf);
-
-	if (mgcp_client_connect(msc_network->mgw.client)) {
-		fprintf(stderr, "MGCPGW connect failed\n");
+	if (msc_mgw_setup() != 0)
 		return 7;
-	}
 
 	if (ss7_setup(tall_msc_ctx, &sccp_a, &sccp_iu)) {
 		fprintf(stderr, "Setting up SCCP client failed.\n");