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

ExternalBridgeSelectionStrategy #889

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jbg
Copy link
Contributor

@jbg jbg commented Mar 10, 2022

This is very similar to a patch we've been using at AVStack for a while, and I think it could be useful for others.

Basically our bridge selection logic is a bit more complex than we'd like to express inside Jicofo itself, so we have Jicofo call an external (to Jicofo) service for bridge selection decisions. A fallback to another strategy is implemented in case it's unable to reach the service.

This lets us look up the customer's bridge preferences in a database (allowing customers to set up rules about which regions are allowed or denied themselves), as well as implement failover to nearby regions etc.

I've marked this as RFC because I'm not sure if it's something you want included in Jicofo itself, and also because the use of HttpClient enforces a Java 11 minimum. I'm not sure if there is a published policy about minimum Java version; if we still need to support older than 11 then we could swap out the use of HttpClient for something else.

@jitsi-jenkins
Copy link

Hi, thanks for your contribution!
If you haven't already done so, could you please make sure you sign our CLA (https://jitsi.org/icla for individuals and https://jitsi.org/ccla for corporations)? We would unfortunately be unable to merge your patch unless we have that piece :(.

@jbg
Copy link
Contributor Author

jbg commented Mar 10, 2022

Well, I guess the existence of CI testing Java 8 tells me that using HttpClient is a no-no — let me know if this is something you'd be interested in merging, and if so I'll work on swapping HttpClient out for something Java 8 friendly.

@saghul
Copy link
Member

saghul commented Mar 10, 2022

We've been deploying jicofo on Java 11 AFAIK. Maybe we ought to update the CI checks? /cc @bgrozev ?

@jbg jbg force-pushed the jbg/external-bridge-selection-strategy branch from 7624ad1 to 4c5d544 Compare March 10, 2022 12:56
@jbg jbg force-pushed the jbg/external-bridge-selection-strategy branch from 4c5d544 to bc8ab65 Compare March 10, 2022 15:53
@bgrozev
Copy link
Member

bgrozev commented Mar 14, 2022

Thanks, that's definitely a good contribution! I'll check back with the team to see if there's anything preventing us from dropping support for java 8.

@damencho
Copy link
Member

Maybe Ubuntu 18.04 ... but that is 4 years old now ...
And anyway it will skip the update and for new installation, and you will need to update java to 11 ... so maybe just edit in the quick install guide as we have for prosody update to 11, that is all we need ...

@jbg jbg changed the title RFC: ExternalBridgeSelectionStrategy ExternalBridgeSelectionStrategy Mar 15, 2022
@bgrozev
Copy link
Member

bgrozev commented Mar 15, 2022

We're working on dropping java8 support, and will review/merge when that's done.

.connectTimeout(ExternalBridgeSelectionStrategyConfig.config.timeout)
.build()

private val logger: Logger = LoggerImpl(ExternalBridgeSelectionStrategy::class.simpleName)
Copy link
Member

Choose a reason for hiding this comment

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

You can use createLogger() here

class ExternalBridgeSelectionStrategy() : BridgeSelectionStrategy() {
private val httpClient: HttpClient = HttpClient
.newBuilder()
.connectTimeout(ExternalBridgeSelectionStrategyConfig.config.timeout)
Copy link
Member

Choose a reason for hiding this comment

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

You can import ExternalBridgeSelectionStrategyConfig.config to make these calls shorter

?: throw Exception("ExternalBridgeSelectionStrategy requires url to be provided")

val requestBody = JSONObject()
requestBody["bridges"] = bridges?.map {
Copy link
Member

Choose a reason for hiding this comment

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

I just realized that you are providing the full list of bridges here. Can you make that optional (behind a config flag) please? We intend to use it with a service which already has the list of available bridges.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's necessary as long as the index is used in the response to identify the bridge. But if the JID is used as suggested, this can even be removed entirely rather than made optional IMO (our service also knows the list of available bridges).

But I was concerned how Jicofo would react if told to use a bridge it doesn't already know about. Even if not deliberate, I can imagine when bridges start up or shut down there could be brief periods where the external selection service has a different idea of available bridges than Jicofo.

}
requestBody["conference_bridges"] = conferenceBridges?.mapKeys { it.key.jid.toString() }
requestBody["participant_region"] = participantRegion
requestBody["fallback_strategy"] = ExternalBridgeSelectionStrategyConfig.config.fallbackStrategy
Copy link
Member

Choose a reason for hiding this comment

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

Curious why you need this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We were using it to signal which mode the external service should use (region based or intra region), matched to the fallback strategy Jicofo would use if it couldn't contact the external service for any reason. We're actually now passing the selected mode in the URL, so this is no longer needed. I'll remove it, absent any use case for it.

try {
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
} catch (exc: Exception) {
logger.error("ExternalBridgeSelectionStrategy: HTTP request failed with ${exc}, using fallback strategy")
Copy link
Member

Choose a reason for hiding this comment

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

Remove "ExternalBridgeSelectionStrategy" from the message, it's already in the logger context.

return fallback(bridges, conferenceBridges, participantRegion)
}

val bridge = bridges!![selectedBridgeIndex.toInt()]
Copy link
Member

Choose a reason for hiding this comment

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

Can you fail early (before sending the request) if bridges is null or empty?

return fallback(bridges, conferenceBridges, participantRegion)
}

val bridge = bridges!![selectedBridgeIndex.toInt()]
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about having the external service return an ID (JID) of the selected bridge instead of an index? We want to have the service return a bridge that jicofo doesn't yet know about (this will require more changes, but it will be easier to have the HTTP API ready to support it and not have to change).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this seems sensible. Interesting use case to use a bridge it doesn't know about yet. Launching bridges on-demand?

.POST(HttpRequest.BodyPublishers.ofString(requestBody.toJSONString()))
.uri(URI.create(url))
.headers("Content-Type", "application/json")
.timeout(ExternalBridgeSelectionStrategyConfig.config.timeout)
Copy link
Member

Choose a reason for hiding this comment

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

Will this work as expected when the timeout is null? You could either only call .timeout if the config has a value, or make the config value required (by config instead of by optionalconfig) and add a reasonable default value in reference.conf

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, .timeout(null) will throw. I think making it required with a default in reference.conf is sensible; there is no rational reason to want to block forever...

}
fun timeout() = timeout

val fallbackStrategy: String? by optionalconfig {
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps it's better to make this required (by config) and provide a good default value?

@bgrozev
Copy link
Member

bgrozev commented Mar 24, 2022

One more thing: can you please add a commented out section to reference.conf with the new options? For the purpose of documentation

@jbg
Copy link
Contributor Author

jbg commented Jan 9, 2023

@bgrozev Thanks for the review and sorry for the long delay before getting back to this. I'll rebase and address your comments shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants