Skip to content
Snippets Groups Projects
Commit a4a0addc authored by Sergiusz Urbaniak's avatar Sergiusz Urbaniak Committed by Andrew Or
Browse files

[SPARK-13492][MESOS] Configurable Mesos framework webui URL.

## What changes were proposed in this pull request?

Previously the Mesos framework webui URL was being derived only from the Spark UI address leaving no possibility to configure it. This commit makes it configurable. If unset it falls back to the previous behavior.

Motivation:
This change is necessary in order to be able to install Spark on DCOS and to be able to give it a custom service link. The configured `webui_url` is configured to point to a reverse proxy in the DCOS environment.

## How was this patch tested?

Locally, using unit tests and on DCOS testing and stable revision.

Author: Sergiusz Urbaniak <sur@mesosphere.io>

Closes #11369 from s-urbaniak/sur-webui-url.
parent 5f7dbdba
No related branches found
No related tags found
No related merge requests found
Showing
with 93 additions and 10 deletions
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
<td class="rowGroupColumn"><span title="{{id}}"><a href="{{url}}">{{id}}</a></span></td> <td class="rowGroupColumn"><span title="{{id}}"><a href="{{url}}">{{id}}</a></span></td>
<td class="rowGroupColumn">{{name}}</td> <td class="rowGroupColumn">{{name}}</td>
{{#attempts}} {{#attempts}}
<td class="attemptIDSpan"><a href="/history/{{id}}/{{attemptId}}/">{{attemptId}}</a></td> <td class="attemptIDSpan"><a href="history/{{id}}/{{attemptId}}/">{{attemptId}}</a></td>
<td>{{startTime}}</td> <td>{{startTime}}</td>
<td>{{endTime}}</td> <td>{{endTime}}</td>
<td><span title="{{duration}}" class="durationClass">{{duration}}</span></td> <td><span title="{{duration}}" class="durationClass">{{duration}}</span></td>
......
...@@ -110,7 +110,7 @@ $(document).ready(function() { ...@@ -110,7 +110,7 @@ $(document).ready(function() {
requestedIncomplete = getParameterByName("showIncomplete", searchString); requestedIncomplete = getParameterByName("showIncomplete", searchString);
requestedIncomplete = (requestedIncomplete == "true" ? true : false); requestedIncomplete = (requestedIncomplete == "true" ? true : false);
$.getJSON("/api/v1/applications", function(response,status,jqXHR) { $.getJSON("api/v1/applications", function(response,status,jqXHR) {
var array = []; var array = [];
var hasMultipleAttempts = false; var hasMultipleAttempts = false;
for (i in response) { for (i in response) {
...@@ -139,9 +139,9 @@ $(document).ready(function() { ...@@ -139,9 +139,9 @@ $(document).ready(function() {
var url = null var url = null
if (maxAttemptId == null) { if (maxAttemptId == null) {
url = "/history/" + id + "/" url = "history/" + id + "/"
} else { } else {
url = "/history/" + id + "/" + maxAttemptId + "/" url = "history/" + id + "/" + maxAttemptId + "/"
} }
var app_clone = {"id" : id, "name" : name, "url" : url, "attempts" : [attempt]}; var app_clone = {"id" : id, "name" : name, "url" : url, "attempts" : [attempt]};
...@@ -150,7 +150,7 @@ $(document).ready(function() { ...@@ -150,7 +150,7 @@ $(document).ready(function() {
} }
var data = {"applications": array} var data = {"applications": array}
$.get("/static/historypage-template.html", function(template) { $.get("static/historypage-template.html", function(template) {
historySummary.append(Mustache.render($(template).filter("#history-summary-template").html(),data)); historySummary.append(Mustache.render($(template).filter("#history-summary-template").html(),data));
var selector = "#history-summary-table"; var selector = "#history-summary-table";
var conf = { var conf = {
......
...@@ -73,7 +73,7 @@ private[mesos] class MesosClusterDispatcher( ...@@ -73,7 +73,7 @@ private[mesos] class MesosClusterDispatcher(
def start(): Unit = { def start(): Unit = {
webUi.bind() webUi.bind()
scheduler.frameworkUrl = webUi.activeWebUiUrl scheduler.frameworkUrl = conf.get("spark.mesos.dispatcher.webui.url", webUi.activeWebUiUrl)
scheduler.start() scheduler.start()
server.start() server.start()
} }
......
...@@ -47,7 +47,7 @@ private[mesos] class MesosClusterDispatcherArguments(args: Array[String], conf: ...@@ -47,7 +47,7 @@ private[mesos] class MesosClusterDispatcherArguments(args: Array[String], conf:
port = value port = value
parse(tail) parse(tail)
case ("--webui-port" | "-p") :: IntParam(value) :: tail => case ("--webui-port") :: IntParam(value) :: tail =>
webUiPort = value webUiPort = value
parse(tail) parse(tail)
......
...@@ -149,7 +149,8 @@ private[spark] class CoarseMesosSchedulerBackend( ...@@ -149,7 +149,8 @@ private[spark] class CoarseMesosSchedulerBackend(
sc.sparkUser, sc.sparkUser,
sc.appName, sc.appName,
sc.conf, sc.conf,
sc.ui.map(_.appUIAddress)) sc.conf.getOption("spark.mesos.driver.webui.url").orElse(sc.ui.map(_.appUIAddress))
)
startScheduler(driver) startScheduler(driver)
} }
......
...@@ -78,7 +78,8 @@ private[spark] class MesosSchedulerBackend( ...@@ -78,7 +78,8 @@ private[spark] class MesosSchedulerBackend(
sc.sparkUser, sc.sparkUser,
sc.appName, sc.appName,
sc.conf, sc.conf,
sc.ui.map(_.appUIAddress)) sc.conf.getOption("spark.mesos.driver.webui.url").orElse(sc.ui.map(_.appUIAddress))
)
startScheduler(driver) startScheduler(driver)
} }
......
...@@ -208,6 +208,35 @@ class CoarseMesosSchedulerBackendSuite extends SparkFunSuite ...@@ -208,6 +208,35 @@ class CoarseMesosSchedulerBackendSuite extends SparkFunSuite
verify(driver, times(1)).killTask(createTaskId("0")) verify(driver, times(1)).killTask(createTaskId("0"))
} }
test("weburi is set in created scheduler driver") {
setBackend()
val taskScheduler = mock[TaskSchedulerImpl]
when(taskScheduler.sc).thenReturn(sc)
val driver = mock[SchedulerDriver]
when(driver.start()).thenReturn(Protos.Status.DRIVER_RUNNING)
val securityManager = mock[SecurityManager]
val backend = new CoarseMesosSchedulerBackend(taskScheduler, sc, "master", securityManager) {
override protected def createSchedulerDriver(
masterUrl: String,
scheduler: Scheduler,
sparkUser: String,
appName: String,
conf: SparkConf,
webuiUrl: Option[String] = None,
checkpoint: Option[Boolean] = None,
failoverTimeout: Option[Double] = None,
frameworkId: Option[String] = None): SchedulerDriver = {
markRegistered()
assert(webuiUrl.isDefined)
assert(webuiUrl.get.equals("http://webui"))
driver
}
}
backend.start()
}
private def verifyDeclinedOffer(driver: SchedulerDriver, private def verifyDeclinedOffer(driver: SchedulerDriver,
offerId: OfferID, offerId: OfferID,
filter: Boolean = false): Unit = { filter: Boolean = false): Unit = {
...@@ -316,6 +345,7 @@ class CoarseMesosSchedulerBackendSuite extends SparkFunSuite ...@@ -316,6 +345,7 @@ class CoarseMesosSchedulerBackendSuite extends SparkFunSuite
.setMaster("local[*]") .setMaster("local[*]")
.setAppName("test-mesos-dynamic-alloc") .setAppName("test-mesos-dynamic-alloc")
.setSparkHome("/path") .setSparkHome("/path")
.set("spark.mesos.driver.webui.url", "http://webui")
if (sparkConfVars != null) { if (sparkConfVars != null) {
for (attr <- sparkConfVars) { for (attr <- sparkConfVars) {
......
...@@ -26,9 +26,9 @@ import scala.collection.JavaConverters._ ...@@ -26,9 +26,9 @@ import scala.collection.JavaConverters._
import scala.collection.mutable import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
import org.apache.mesos.{Protos, Scheduler, SchedulerDriver}
import org.apache.mesos.Protos._ import org.apache.mesos.Protos._
import org.apache.mesos.Protos.Value.Scalar import org.apache.mesos.Protos.Value.Scalar
import org.apache.mesos.SchedulerDriver
import org.mockito.{ArgumentCaptor, Matchers} import org.mockito.{ArgumentCaptor, Matchers}
import org.mockito.Matchers._ import org.mockito.Matchers._
import org.mockito.Mockito._ import org.mockito.Mockito._
...@@ -42,6 +42,41 @@ import org.apache.spark.scheduler.cluster.ExecutorInfo ...@@ -42,6 +42,41 @@ import org.apache.spark.scheduler.cluster.ExecutorInfo
class MesosSchedulerBackendSuite extends SparkFunSuite with LocalSparkContext with MockitoSugar { class MesosSchedulerBackendSuite extends SparkFunSuite with LocalSparkContext with MockitoSugar {
test("weburi is set in created scheduler driver") {
val conf = new SparkConf
conf.set("spark.mesos.driver.webui.url", "http://webui")
conf.set("spark.app.name", "name1")
val sc = mock[SparkContext]
when(sc.conf).thenReturn(conf)
when(sc.sparkUser).thenReturn("sparkUser1")
when(sc.appName).thenReturn("appName1")
val taskScheduler = mock[TaskSchedulerImpl]
val driver = mock[SchedulerDriver]
when(driver.start()).thenReturn(Protos.Status.DRIVER_RUNNING)
val backend = new MesosSchedulerBackend(taskScheduler, sc, "master") {
override protected def createSchedulerDriver(
masterUrl: String,
scheduler: Scheduler,
sparkUser: String,
appName: String,
conf: SparkConf,
webuiUrl: Option[String] = None,
checkpoint: Option[Boolean] = None,
failoverTimeout: Option[Double] = None,
frameworkId: Option[String] = None): SchedulerDriver = {
markRegistered()
assert(webuiUrl.isDefined)
assert(webuiUrl.get.equals("http://webui"))
driver
}
}
backend.start()
}
test("Use configured mesosExecutor.cores for ExecutorInfo") { test("Use configured mesosExecutor.cores for ExecutorInfo") {
val mesosExecutorCores = 3 val mesosExecutorCores = 3
val conf = new SparkConf val conf = new SparkConf
......
...@@ -390,6 +390,22 @@ See the [configuration page](configuration.html) for information on Spark config ...@@ -390,6 +390,22 @@ See the [configuration page](configuration.html) for information on Spark config
</ul> </ul>
</td> </td>
</tr> </tr>
<tr>
<td><code>spark.mesos.driver.webui.url</code></td>
<td><code>(none)</code></td>
<td>
Set the Spark Mesos driver webui_url for interacting with the framework.
If unset it will point to Spark's internal web UI.
</td>
</tr>
<tr>
<td><code>spark.mesos.dispatcher.webui.url</code></td>
<td><code>(none)</code></td>
<td>
Set the Spark Mesos dispatcher webui_url for interacting with the framework.
If unset it will point to Spark's internal web UI.
</td>
</tr>
</table> </table>
# Troubleshooting and Debugging # Troubleshooting and Debugging
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment