Skip to content

Commit 7624ad1

Browse files
committed
Added ExternalBridgeSelectionStrategy and related config
1 parent f63d169 commit 7624ad1

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package org.jitsi.jicofo.bridge
2+
3+
import org.jitsi.utils.logging2.Logger
4+
import org.jitsi.utils.logging2.LoggerImpl
5+
import org.json.simple.JSONObject
6+
import org.json.simple.JSONValue
7+
import java.net.URI
8+
import java.net.http.HttpClient
9+
import java.net.http.HttpRequest
10+
import java.net.http.HttpResponse
11+
12+
@Suppress("unused")
13+
class ExternalBridgeSelectionStrategy() : BridgeSelectionStrategy() {
14+
private val httpClient: HttpClient = HttpClient
15+
.newBuilder()
16+
.connectTimeout(ExternalBridgeSelectionStrategyConfig.config.timeout)
17+
.build()
18+
19+
private val logger: Logger = LoggerImpl(ExternalBridgeSelectionStrategy::class.simpleName)
20+
21+
private val fallbackStrategy: BridgeSelectionStrategy? by lazy {
22+
val fallbackStrategyName = ExternalBridgeSelectionStrategyConfig.config.fallbackStrategy ?: return@lazy null
23+
try {
24+
val clazz = Class.forName("${javaClass.getPackage().name}.$fallbackStrategyName")
25+
clazz.getConstructor().newInstance() as BridgeSelectionStrategy
26+
} catch (e: Exception) {
27+
val clazz = Class.forName(fallbackStrategyName)
28+
clazz.getConstructor().newInstance() as BridgeSelectionStrategy
29+
}
30+
}
31+
32+
private fun fallback(
33+
bridges: MutableList<Bridge>?,
34+
conferenceBridges: MutableMap<Bridge, Int>?,
35+
participantRegion: String?
36+
): Bridge {
37+
if (fallbackStrategy == null) {
38+
throw Exception("External bridge selection failed and no fallbackStrategy was provided.")
39+
}
40+
return fallbackStrategy!!.doSelect(bridges, conferenceBridges, participantRegion)
41+
}
42+
43+
override fun doSelect(
44+
bridges: MutableList<Bridge>?,
45+
conferenceBridges: MutableMap<Bridge, Int>?,
46+
participantRegion: String?
47+
): Bridge {
48+
val url = ExternalBridgeSelectionStrategyConfig.config.url
49+
?: throw Exception("ExternalBridgeSelectionStrategy requires url to be provided")
50+
51+
val requestBody = JSONObject()
52+
requestBody["bridges"] = bridges?.map {
53+
mapOf(
54+
"jid" to it.jid.toString(),
55+
"version" to it.version,
56+
"colibri2" to it.supportsColibri2(),
57+
"relay_id" to it.relayId,
58+
"region" to it.region,
59+
"stress" to it.stress,
60+
"operational" to it.isOperational,
61+
"graceful_shutdown" to it.isInGracefulShutdown,
62+
"draining" to it.isDraining,
63+
)
64+
}
65+
requestBody["conference_bridges"] = conferenceBridges?.mapKeys { it.key.jid.toString() }
66+
requestBody["participant_region"] = participantRegion
67+
requestBody["fallback_strategy"] = ExternalBridgeSelectionStrategyConfig.config.fallbackStrategy
68+
69+
val request = HttpRequest
70+
.newBuilder()
71+
.POST(HttpRequest.BodyPublishers.ofString(requestBody.toJSONString()))
72+
.uri(URI.create(url))
73+
.headers("Content-Type", "application/json")
74+
.timeout(ExternalBridgeSelectionStrategyConfig.config.timeout)
75+
.build()
76+
77+
val response: HttpResponse<String>
78+
try {
79+
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
80+
} catch (exc: Exception) {
81+
logger.error("ExternalBridgeSelectionStrategy: HTTP request failed with ${exc}, using fallback strategy")
82+
return fallback(bridges, conferenceBridges, participantRegion)
83+
}
84+
85+
val statusCode = response.statusCode()
86+
if (statusCode !in 200..299) {
87+
logger.error("ExternalBridgeSelectionStrategy: HTTP request failed with ${statusCode}, using fallback strategy")
88+
return fallback(bridges, conferenceBridges, participantRegion)
89+
}
90+
91+
val responseBody: JSONObject
92+
try {
93+
responseBody = JSONValue.parseWithException(response.body()) as JSONObject
94+
} catch (exc: Exception) {
95+
logger.error("ExternalBridgeSelectionStrategy: HTTP response parsing failed with ${exc}, using fallback strategy")
96+
return fallback(bridges, conferenceBridges, participantRegion)
97+
}
98+
99+
val selectedBridgeIndex = responseBody["selectedBridgeIndex"] as? Int
100+
101+
if (selectedBridgeIndex == null) {
102+
logger.error("ExternalBridgeSelectionStrategy: HTTP response selectedBridgeIndex missing or invalid, using fallback strategy")
103+
return fallback(bridges, conferenceBridges, participantRegion)
104+
}
105+
106+
val bridge = bridges!![selectedBridgeIndex]
107+
logger.info("ExternalBridgeSelectionStrategy: participantRegion=${participantRegion}, bridge=${bridge}")
108+
return bridge
109+
}
110+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.jitsi.jicofo.bridge
2+
3+
import org.jitsi.config.JitsiConfig
4+
import org.jitsi.metaconfig.optionalconfig
5+
import java.time.Duration
6+
7+
class ExternalBridgeSelectionStrategyConfig private constructor() {
8+
val url: String? by optionalconfig {
9+
"${BASE}.url".from(JitsiConfig.newConfig)
10+
}
11+
fun url() = url
12+
13+
val timeout: Duration? by optionalconfig {
14+
"${BASE}.timeout".from(JitsiConfig.newConfig)
15+
}
16+
fun timeout() = timeout
17+
18+
val fallbackStrategy: String? by optionalconfig {
19+
"${BASE}.fallback-strategy".from(JitsiConfig.newConfig)
20+
}
21+
fun fallbackStrategy() = fallbackStrategy
22+
23+
companion object {
24+
const val BASE = "jicofo.bridge.external-selection-strategy"
25+
26+
@JvmField
27+
val config = ExternalBridgeSelectionStrategyConfig()
28+
}
29+
}

0 commit comments

Comments
 (0)