Skip to content

Commit b9d0612

Browse files
authored
feat: Add an API to move endpoints. (#1161)
* feat: Add an API to move endpoints.
1 parent 213f776 commit b9d0612

File tree

9 files changed

+369
-30
lines changed

9 files changed

+369
-30
lines changed

jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeSelector.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.jitsi.xmpp.extensions.colibri.ColibriStatsExtension
2727
import org.json.simple.JSONObject
2828
import org.jxmpp.jid.Jid
2929
import java.time.Clock
30+
import java.util.concurrent.ConcurrentHashMap
3031
import java.util.concurrent.Executors
3132

3233
/**
@@ -50,17 +51,16 @@ class BridgeSelector @JvmOverloads constructor(
5051
fun addHandler(eventHandler: EventHandler) = eventEmitter.addHandler(eventHandler)
5152
fun removeHandler(eventHandler: EventHandler) = eventEmitter.removeHandler(eventHandler)
5253

53-
/**
54-
* The bridge selection strategy.
55-
*/
54+
/** The bridge selection strategy. */
5655
private val bridgeSelectionStrategy = BridgeConfig.config.selectionStrategy.also {
5756
logger.info("Using ${it.javaClass.name}")
5857
}
5958

60-
/**
61-
* The map of bridge JID to <tt>Bridge</tt>.
62-
*/
63-
private val bridges: MutableMap<Jid, Bridge> = mutableMapOf()
59+
/** The map of bridge JID to <tt>Bridge</tt>. */
60+
private val bridges: MutableMap<Jid, Bridge> = ConcurrentHashMap()
61+
62+
/** Get the [Bridge] with a specific JID or null */
63+
fun get(jid: Jid) = bridges[jid]
6464

6565
init {
6666
JicofoMetricsContainer.instance.metricsUpdater.addUpdateTask { updateMetrics() }

jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriSessionManager.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.jitsi.jicofo.bridge.colibri
1919

2020
import org.jitsi.jicofo.bridge.Bridge
21+
import org.jitsi.jicofo.bridge.ConferenceBridgeProperties
2122
import org.jitsi.jicofo.conference.source.EndpointSourceSet
2223
import org.jitsi.utils.MediaType
2324
import org.jitsi.utils.OrderedJsonObject
@@ -41,8 +42,12 @@ interface ColibriSessionManager {
4142
val bridgeCount: Int
4243
val bridgeRegions: Set<String>
4344

45+
/** Get the list of participant IDs that are currently allocated on a specific [Bridge]. */
46+
fun getParticipants(bridge: Bridge): List<String>
47+
4448
@Throws(ColibriAllocationFailedException::class, BridgeSelectionFailedException::class)
4549
fun allocate(participant: ParticipantAllocationParameters): ColibriAllocation
50+
fun getBridges(): Map<Bridge, ConferenceBridgeProperties>
4651

4752
fun updateParticipant(
4853
participantId: String,

jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriV2SessionManager.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class ColibriV2SessionManager(
8282
}
8383

8484
/**
85-
* The colibri2 sessions that are currently active, mapped by the [Bridge] that they use.
85+
* The colibri2 sessions that are currently active, mapped by the relayId of the [Bridge] that they use.
8686
*/
8787
override val sessions = mutableMapOf<String?, Colibri2Session>()
8888

@@ -92,15 +92,18 @@ class ColibriV2SessionManager(
9292
*/
9393
private val participants = mutableMapOf<String, ParticipantInfo>()
9494

95+
override fun getParticipants(bridge: Bridge): List<String> = synchronized(syncRoot) {
96+
val session = sessions[bridge.relayId] ?: return emptyList()
97+
return getSessionParticipants(session).map { it.id }
98+
}
99+
95100
/**
96101
* Maintains the same set as [participants], but organized by their session. Needs to be kept in sync with
97102
* [participants] (see [add], [remove], [clear]).
98103
*/
99104
private val participantsBySession = mutableMapOf<Colibri2Session, MutableList<ParticipantInfo>>()
100105

101-
/**
102-
* Protects access to [sessions], [participants] and [participantsBySession].
103-
*/
106+
/** Protects access to [sessions], [participants] and [participantsBySession]. */
104107
private val syncRoot = Any()
105108

106109
/**
@@ -243,7 +246,7 @@ class ColibriV2SessionManager(
243246
}
244247

245248
/** Get the bridge-to-bridge-properties map needed for bridge selection. */
246-
private fun getBridges(): Map<Bridge, ConferenceBridgeProperties> = synchronized(syncRoot) {
249+
override fun getBridges(): Map<Bridge, ConferenceBridgeProperties> = synchronized(syncRoot) {
247250
return participantsBySession.entries
248251
.filter { it.key.bridge.isOperational }
249252
.associate {

jicofo-selector/src/main/resources/reference.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,10 @@ jicofo {
338338
// Enable the conference-request endpoint
339339
enabled = true
340340
}
341+
move-endpoints {
342+
// Enable the move-endpoint API.
343+
enabled = true
344+
}
341345
}
342346

343347
sctp {

jicofo/src/main/java/org/jitsi/jicofo/conference/JitsiMeetConference.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.jitsi.jicofo.conference;
1919

2020
import org.jetbrains.annotations.*;
21+
import org.jitsi.jicofo.bridge.*;
2122
import org.jitsi.jicofo.jibri.*;
2223
import org.jitsi.jicofo.xmpp.*;
2324
import org.jitsi.jicofo.xmpp.muc.*;
@@ -150,4 +151,20 @@ MuteResult handleMuteRequest(
150151
/** Get the stats for this conference that should be exported to rtcstats. */
151152
@NotNull
152153
OrderedJsonObject getRtcstatsState();
154+
155+
/** Move (reinvite) an endpoint in this conference. Return true if the endpoint was moved. */
156+
boolean moveEndpoint(@NotNull String endpointId, Bridge bridge);
157+
158+
/**
159+
* Move (reinvite) a specific number of endpoints from the conference from a specific bridge. The implementation
160+
* decides which endpoints to move.
161+
*
162+
* @param bridge the bridge from which to move endpoints.
163+
* @param numEps the number of endpoints to move.
164+
* @return the number of endpoints moved.
165+
*/
166+
int moveEndpoints(@NotNull Bridge bridge, int numEps);
167+
168+
/** Get information about the bridges currently used by this conference. */
169+
Map<Bridge, ConferenceBridgeProperties> getBridges();
153170
}

jicofo/src/main/java/org/jitsi/jicofo/conference/JitsiMeetConferenceImpl.java

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,6 +1686,56 @@ public long getVisitorCount()
16861686
}
16871687
}
16881688

1689+
public Map<Bridge, ConferenceBridgeProperties> getBridges()
1690+
{
1691+
ColibriSessionManager colibriSessionManager = this.colibriSessionManager;
1692+
if (colibriSessionManager == null)
1693+
{
1694+
return Collections.emptyMap();
1695+
}
1696+
return colibriSessionManager.getBridges();
1697+
}
1698+
1699+
@Override
1700+
public boolean moveEndpoint(@NotNull String endpointId, Bridge bridge)
1701+
{
1702+
if (bridge != null)
1703+
{
1704+
List<String> bridgeParticipants = colibriSessionManager.getParticipants(bridge);
1705+
if (!bridgeParticipants.contains(endpointId))
1706+
{
1707+
logger.warn("Endpoint " + endpointId + " is not connected to bridge " + bridge.getJid());
1708+
return false;
1709+
}
1710+
}
1711+
ColibriSessionManager colibriSessionManager = this.colibriSessionManager;
1712+
if (colibriSessionManager == null)
1713+
{
1714+
return false;
1715+
}
1716+
1717+
colibriSessionManager.removeParticipant(endpointId);
1718+
return reInviteParticipantsById(Collections.singletonList(endpointId)) == 1;
1719+
}
1720+
1721+
@Override
1722+
public int moveEndpoints(@NotNull Bridge bridge, int numEps)
1723+
{
1724+
logger.info("Moving " + numEps + " endpoints from " + bridge.getJid());
1725+
ColibriSessionManager colibriSessionManager = this.colibriSessionManager;
1726+
if (colibriSessionManager == null)
1727+
{
1728+
return 0;
1729+
}
1730+
List<String> participantIds
1731+
= colibriSessionManager.getParticipants(bridge).stream().limit(numEps).collect(Collectors.toList());
1732+
for (String participantId : participantIds)
1733+
{
1734+
colibriSessionManager.removeParticipant(participantId);
1735+
}
1736+
return reInviteParticipantsById(participantIds);
1737+
}
1738+
16891739
/**
16901740
* Checks whether a request for a new endpoint to join this conference should be redirected to a visitor node.
16911741
* @return the name of the visitor node if it should be redirected, and null otherwise.
@@ -1887,33 +1937,41 @@ private void onBridgeUp(Jid bridgeJid)
18871937
}
18881938
}
18891939

1890-
private void reInviteParticipantsById(@NotNull List<String> participantIdsToReinvite)
1940+
private int reInviteParticipantsById(@NotNull List<String> participantIdsToReinvite)
18911941
{
1892-
reInviteParticipantsById(participantIdsToReinvite, true);
1942+
return reInviteParticipantsById(participantIdsToReinvite, true);
18931943
}
18941944

1895-
private void reInviteParticipantsById(@NotNull List<String> participantIdsToReinvite, boolean updateParticipant)
1945+
private int reInviteParticipantsById(@NotNull List<String> participantIdsToReinvite, boolean updateParticipant)
18961946
{
1897-
if (!participantIdsToReinvite.isEmpty())
1947+
int n = participantIdsToReinvite.size();
1948+
if (n == 0)
18981949
{
1899-
ConferenceMetrics.participantsMoved.addAndGet(participantIdsToReinvite.size());
1900-
synchronized (participantLock)
1950+
return 0;
1951+
}
1952+
1953+
List<Participant> participantsToReinvite = new ArrayList<>();
1954+
synchronized (participantLock)
1955+
{
1956+
for (Participant participant : participants.values())
19011957
{
1902-
List<Participant> participantsToReinvite = new ArrayList<>();
1903-
for (Participant participant : participants.values())
1958+
if (participantsToReinvite.size() == n)
19041959
{
1905-
if (participantIdsToReinvite.contains(participant.getEndpointId()))
1906-
{
1907-
participantsToReinvite.add(participant);
1908-
}
1960+
break;
19091961
}
1910-
if (participantsToReinvite.size() != participantIdsToReinvite.size())
1962+
if (participantIdsToReinvite.contains(participant.getEndpointId()))
19111963
{
1912-
logger.error("Can not re-invite all participants, no Participant object for some of them.");
1964+
participantsToReinvite.add(participant);
19131965
}
1914-
reInviteParticipants(participantsToReinvite, updateParticipant);
19151966
}
1967+
if (participantsToReinvite.size() != participantIdsToReinvite.size())
1968+
{
1969+
logger.error("Can not re-invite all participants, no Participant object for some of them.");
1970+
}
1971+
reInviteParticipants(participantsToReinvite, updateParticipant);
19161972
}
1973+
ConferenceMetrics.participantsMoved.addAndGet(participantsToReinvite.size());
1974+
return participantsToReinvite.size();
19171975
}
19181976

19191977
/**

jicofo/src/main/kotlin/org/jitsi/jicofo/JicofoServices.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import org.jitsi.jicofo.metrics.JicofoMetricsContainer
3939
import org.jitsi.jicofo.rest.Application
4040
import org.jitsi.jicofo.rest.ConferenceRequest
4141
import org.jitsi.jicofo.rest.RestConfig
42+
import org.jitsi.jicofo.rest.move.MoveEndpoints
43+
import org.jitsi.jicofo.rest.move.MoveEndpointsConfig
4244
import org.jitsi.jicofo.util.SynchronizedDelegate
4345
import org.jitsi.jicofo.version.CurrentVersionImpl
4446
import org.jitsi.jicofo.xmpp.XmppServices
@@ -147,8 +149,7 @@ class JicofoServices {
147149
jettyServer = if (RestConfig.config.enabled) {
148150
logger.info("Starting HTTP server with config: ${RestConfig.config.httpServerConfig}.")
149151
val restApp = Application(
150-
buildList
151-
{
152+
buildList {
152153
healthChecker?.let {
153154
add(org.jitsi.rest.Health(it))
154155
}
@@ -159,6 +160,9 @@ class JicofoServices {
159160
if (RestConfig.config.enablePrometheus) {
160161
add(Prometheus(JicofoMetricsContainer.instance))
161162
}
163+
if (MoveEndpointsConfig.enabled) {
164+
add(MoveEndpoints(focusManager, bridgeSelector))
165+
}
162166
}
163167
)
164168
createServer(RestConfig.config.httpServerConfig).also {

jicofo/src/main/kotlin/org/jitsi/jicofo/rest/ConferenceRequest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,6 @@ class ConferenceRequest(
8282
* in the constructor below) is necessary. This class exists in order to expose
8383
* that behavior in a more concise way
8484
*/
85-
private class BadRequestExceptionWithMessage(message: String?) : BadRequestException(
85+
class BadRequestExceptionWithMessage(message: String?) : BadRequestException(
8686
Response.status(HttpServletResponse.SC_BAD_REQUEST, message).build()
8787
)

0 commit comments

Comments
 (0)