Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace jetty with ktor. #1178

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
10 changes: 1 addition & 9 deletions jicofo/src/main/kotlin/org/jitsi/jicofo/JicofoServices.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ import org.jitsi.jicofo.metrics.GlobalMetrics
import org.jitsi.jicofo.metrics.JicofoMetricsContainer
import org.jitsi.jicofo.rest.Application
import org.jitsi.jicofo.rest.RestConfig
import org.jitsi.jicofo.rest.move.MoveEndpoints
import org.jitsi.jicofo.rest.move.MoveEndpointsConfig
import org.jitsi.jicofo.util.SynchronizedDelegate
import org.jitsi.jicofo.version.CurrentVersionImpl
import org.jitsi.jicofo.xmpp.XmppServices
Expand Down Expand Up @@ -141,7 +139,7 @@ class JicofoServices {
}

private val ktor = if (RestConfig.config.enabled) {
org.jitsi.jicofo.ktor.Application(healthChecker, xmppServices.conferenceIqHandler)
org.jitsi.jicofo.ktor.Application(healthChecker, xmppServices.conferenceIqHandler, focusManager, bridgeSelector)
} else {
logger.info("Rest interface disabled.")
null
Expand All @@ -153,12 +151,6 @@ class JicofoServices {
logger.info("Starting HTTP server with config: ${RestConfig.config.httpServerConfig}.")
val restApp = Application(
buildList {
if (RestConfig.config.enableConferenceRequest) {
//add(ConferenceRequest(xmppServices.conferenceIqHandler))
}
if (MoveEndpointsConfig.enabled) {
add(MoveEndpoints(focusManager, bridgeSelector))
}
}
)
createServer(RestConfig.config.httpServerConfig).also {
Expand Down
48 changes: 41 additions & 7 deletions jicofo/src/main/kotlin/org/jitsi/jicofo/ktor/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import org.jitsi.health.HealthCheckService
import org.jitsi.jicofo.ConferenceRequest
import org.jitsi.jicofo.ConferenceStore
import org.jitsi.jicofo.bridge.BridgeSelector
import org.jitsi.jicofo.ktor.exception.ExceptionHandler
import org.jitsi.jicofo.metrics.JicofoMetricsContainer
import org.jitsi.jicofo.rest.RestConfig
Expand All @@ -47,11 +49,14 @@ import org.jitsi.utils.logging2.createLogger

class Application(
private val healthChecker: HealthCheckService?,
conferenceIqHandler: ConferenceIqHandler
conferenceIqHandler: ConferenceIqHandler,
conferenceStore: ConferenceStore,
bridgeSelector: BridgeSelector
) {
private val logger = createLogger()
private val server = start()
private val conferenceRequestHandler = ConferenceRequestHandler(conferenceIqHandler)
private val moveEndpointsHandler = MoveEndpoints(conferenceStore, bridgeSelector)

private fun start(): EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration> {
logger.info("Starting ktor on port 9999")
Expand All @@ -76,19 +81,15 @@ class Application(
}
about()
conferenceRequest()
moveEndpoints()
}
}.start(wait = false)
}

fun stop() = server.stop()

private fun Route.about() {
data class VersionInfo(
val name: String? = null,
val version: String? = null,
val os: String? = null
)

data class VersionInfo(val name: String? = null, val version: String? = null, val os: String? = null)
val versionInfo = VersionInfo(
CurrentVersionImpl.VERSION.applicationName,
CurrentVersionImpl.VERSION.toString(),
Expand Down Expand Up @@ -125,4 +126,37 @@ class Application(
}
}
}

private fun Route.moveEndpoints() {
if (RestConfig.config.enableMoveEndpoints) {
route("/move-endpoints") {
get("move-endpoint") {
call.respond(
moveEndpointsHandler.moveEndpoint(
call.request.queryParameters["conference"],
call.request.queryParameters["endpoint"],
call.request.queryParameters["bridge"],
)
)
}
get("move-endpoints") {
call.respond(
moveEndpointsHandler.moveEndpoints(
call.request.queryParameters["bridge"],
call.request.queryParameters["conference"],
call.request.queryParameters["numEndpoints"]?.toInt() ?: 1
)
)
}
get("move-endpoints") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be "move-fraction"?

call.respond(
moveEndpointsHandler.moveFraction(
call.request.queryParameters["bridge"],
call.request.queryParameters["fraction"]?.toDouble() ?: 0.1
)
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.jicofo.rest.move
package org.jitsi.jicofo.ktor

import jakarta.servlet.http.HttpServletResponse
import jakarta.ws.rs.DefaultValue
import jakarta.ws.rs.GET
import jakarta.ws.rs.NotFoundException
import jakarta.ws.rs.Path
import jakarta.ws.rs.Produces
import jakarta.ws.rs.QueryParam
import jakarta.ws.rs.core.MediaType
import jakarta.ws.rs.core.Response
import org.jitsi.config.JitsiConfig
import org.jitsi.jicofo.ConferenceStore
import org.jitsi.jicofo.bridge.Bridge
import org.jitsi.jicofo.bridge.BridgeConfig
import org.jitsi.jicofo.bridge.BridgeSelector
import org.jitsi.jicofo.conference.JitsiMeetConference
import org.jitsi.jicofo.rest.BadRequestExceptionWithMessage
import org.jitsi.metaconfig.config
import org.jitsi.jicofo.ktor.exception.BadRequest
import org.jitsi.jicofo.ktor.exception.MissingParameter
import org.jitsi.jicofo.ktor.exception.NotFound
import org.jitsi.utils.logging2.createLogger
import org.jxmpp.jid.impl.JidCreate
import kotlin.math.min
Expand All @@ -44,7 +35,6 @@ import kotlin.math.roundToInt
* that when re-inviting the normal bridge selection logic is used again, so it's possible that the same bridge is
* selected (unless it's unhealthy/draining or overloaded and there are less loaded bridges).
*/
@Path("/move-endpoints")
class MoveEndpoints(
val conferenceStore: ConferenceStore,
val bridgeSelector: BridgeSelector
Expand All @@ -54,28 +44,19 @@ class MoveEndpoints(
/**
* Move a specific endpoint in a specific conference.
*/
@Path("move-endpoint")
@GET
@Produces(MediaType.APPLICATION_JSON)
fun moveEndpoint(
/**
* Conference JID, e.g [email protected]. This is a required parameter, but without a @DefaultValue
* jetty returns a 500 and prints a stack trace.
*/
@QueryParam("conference") @DefaultValue("") conferenceId: String,
/**
* Endpoint ID, e.g. abcdefgh. This is a required parameter, but without a @DefaultValue jetty returns a 500
* and prints a stack trace.
*/
@QueryParam("endpoint") @DefaultValue("") endpointId: String,
/** Conference JID, e.g [email protected]. */
conferenceId: String?,
/** Endpoint ID, e.g. abcdefgh. */
endpointId: String?,
/**
* Optional bridge JID. If specified, the endpoint will only be moved it if is indeed connected to this bridge.
*/
@QueryParam("bridge") @DefaultValue("") bridgeId: String
bridgeId: String?
): Result {
if (conferenceId.isEmpty()) throw BadRequestExceptionWithMessage("Conference ID is missing")
if (endpointId.isEmpty()) throw BadRequestExceptionWithMessage("Endpoint ID is missing")
val bridge = if (bridgeId.isEmpty()) null else getBridge(bridgeId)
if (conferenceId.isNullOrBlank()) throw MissingParameter("conference")
if (endpointId.isNullOrBlank()) throw MissingParameter("endpoint")
val bridge = if (bridgeId.isNullOrBlank()) null else getBridge(bridgeId)
val conference = getConference(conferenceId)

logger.info("Moving conference=$conferenceId endpoint=$endpointId bridge=$bridgeId")
Expand All @@ -98,26 +79,20 @@ class MoveEndpoints(
* greedily from the list until we've selected the desired count. Note that this may need to be adjusted if it leads
* to thundering horde issues (though the recentlyAddedEndpointCount correction should prevent them).
*/
@Path("move-endpoints")
@GET
@Produces(MediaType.APPLICATION_JSON)
fun moveEndpoints(
/**
* Bridge JID, e.g. [email protected]/jvb1. This is a required parameter, but without a
* @DefaultValue jetty returns a 500 and prints a stack trace.
*/
@QueryParam("bridge") @DefaultValue("") bridgeId: String,
/** Bridge JID, e.g. [email protected]/jvb1. */
bridgeId: String?,
/**
* Optional conference JID, e.g [email protected]. If specified only endpoints from this conference
* will be moved.
*/
@QueryParam("conference") @DefaultValue("") conferenceId: String,
conferenceId: String?,
/** Number of endpoints to move. */
@QueryParam("endpoints") @DefaultValue("1") numEndpoints: Int
numEndpoints: Int
): Result {
if (bridgeId.isEmpty()) throw BadRequestExceptionWithMessage("Bridge JID is missing")
if (bridgeId.isNullOrBlank()) throw MissingParameter("bridge")
val bridge = getBridge(bridgeId)
val conference = if (conferenceId.isEmpty()) null else getConference(conferenceId)
val conference = if (conferenceId.isNullOrBlank()) null else getConference(conferenceId)
val bridgeConferences = if (conference == null) {
bridge.getConferences()
} else {
Expand All @@ -136,19 +111,13 @@ class MoveEndpoints(
* that this may need to be adjusted if it leads to thundering horde issues (though the recentlyAddedEndpointCount
* correction should prevent them).
*/
@Path("move-fraction")
@GET
@Produces(MediaType.APPLICATION_JSON)
fun moveFraction(
/**
* Bridge JID, e.g. [email protected]/jvb1. This is a required parameter, but without a
* @DefaultValue jetty returns a 500 and prints a stack trace.
*/
@QueryParam("bridge") @DefaultValue("") bridgeId: String,
/** The fraction of endpoints to move. Defaults to 10% */
@QueryParam("fraction") @DefaultValue("0.1") fraction: Double
/** Bridge JID, e.g. [email protected]/jvb1. */
bridgeId: String?,
/** The fraction of endpoints to move. */
fraction: Double
): Result {
if (bridgeId.isEmpty()) throw BadRequestExceptionWithMessage("Bridge JID is missing")
if (bridgeId.isNullOrBlank()) throw MissingParameter("bridge")
val bridge = getBridge(bridgeId)
val bridgeConferences = bridge.getConferences()
val totalEndpoints = bridgeConferences.sumOf { it.second }
Expand All @@ -175,27 +144,26 @@ class MoveEndpoints(
val bridgeJid = try {
JidCreate.from(bridge)
} catch (e: Exception) {
throw BadRequestExceptionWithMessage("Invalid bridge ID")
throw BadRequest("Invalid bridge ID")
}

bridgeSelector.get(bridgeJid)?.let { return it }

val bridgeFullJid = try {
JidCreate.from("${BridgeConfig.config.breweryJid}/$bridge")
} catch (e: Exception) {
throw BadRequestExceptionWithMessage("Invalid bridge ID")
throw BadRequest("Invalid bridge ID")
}
return bridgeSelector.get(bridgeFullJid) ?: throw NotFoundExceptionWithMessage("Bridge not found")
return bridgeSelector.get(bridgeFullJid) ?: throw NotFound("Bridge not found")
}

private fun getConference(conferenceId: String): JitsiMeetConference {
val conferenceJid = try {
JidCreate.entityBareFrom(conferenceId)
} catch (e: Exception) {
throw BadRequestExceptionWithMessage("Invalid conference ID")
throw BadRequest("Invalid conference ID")
}
return conferenceStore.getConference(conferenceJid)
?: throw NotFoundExceptionWithMessage("Conference not found")
return conferenceStore.getConference(conferenceJid) ?: throw NotFound("Conference not found")
}

private fun Bridge.getConferences() = conferenceStore.getAllConferences().mapNotNull { conference ->
Expand All @@ -208,14 +176,6 @@ data class Result(
val conferences: Int
)

class MoveEndpointsConfig {
companion object {
val enabled: Boolean by config {
"jicofo.rest.move-endpoints.enabled".from(JitsiConfig.newConfig)
}
}
}

/**
* Select endpoints to move, e.g. with a map m={a: 1, b: 3, c: 3}:
* select(m, 1) should return {a: 1}
Expand All @@ -238,11 +198,4 @@ private fun <T> List<Pair<T, Int>>.select(n: Int): Map<T, Int> {
put(it.first, m)
}
}
}

/**
* The [NotFoundException(String message)] constructor doesn't actually include the message in the response.
*/
class NotFoundExceptionWithMessage(message: String?) : NotFoundException(
Response.status(HttpServletResponse.SC_NOT_FOUND, message).build()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respondText

sealed class JicofoKtorException(message: String?) : RuntimeException(message)
class BadRequest(message: String? = null) : JicofoKtorException("Bad request: ${message ?: ""}")
open class BadRequest(message: String? = null) : JicofoKtorException("Bad request: ${message ?: ""}")
class NotFound(message: String?) : JicofoKtorException("Not found: ${message ?: ""}")
class Forbidden(message: String? = null) : JicofoKtorException("Forbidden: ${message ?: ""}")
class InternalError(message: String? = null): JicofoKtorException("Internal error: ${message ?: ""}")
class MissingParameter(parameter: String) : BadRequest("Missing parameter: $parameter")

object ExceptionHandler {
suspend fun handle(call: ApplicationCall, cause: Throwable) {
Expand Down
4 changes: 4 additions & 0 deletions jicofo/src/main/kotlin/org/jitsi/jicofo/rest/RestConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class RestConfig private constructor() {
"jicofo.rest.conference-request.enabled".from(JitsiConfig.newConfig)
}

val enableMoveEndpoints: Boolean by config {
"jicofo.rest.move-endpoints.enabled".from(JitsiConfig.newConfig)
}

companion object {
@JvmField
val config = RestConfig()
Expand Down