Skip to content

Commit 8207428

Browse files
committed
Refactor mesos scheduler resourceOffers and add unit test
1 parent 7f37188 commit 8207428

File tree

2 files changed

+120
-54
lines changed

2 files changed

+120
-54
lines changed

core/src/main/scala/org/apache/spark/scheduler/cluster/mesos/MesosSchedulerBackend.scala

Lines changed: 36 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ private[spark] class MesosSchedulerBackend(
200200
}
201201
}
202202

203+
def toWorkerOffer(offer: Offer) = new WorkerOffer(
204+
offer.getSlaveId.getValue,
205+
offer.getHostname,
206+
getResource(offer.getResourcesList, "cpus").toInt)
207+
203208
override def disconnected(d: SchedulerDriver) {}
204209

205210
override def reregistered(d: SchedulerDriver, masterInfo: MasterInfo) {}
@@ -212,62 +217,39 @@ private[spark] class MesosSchedulerBackend(
212217
override def resourceOffers(d: SchedulerDriver, offers: JList[Offer]) {
213218
val oldClassLoader = setClassLoader()
214219
try {
215-
synchronized {
216-
// Build a big list of the offerable workers, and remember their indices so that we can
217-
// figure out which Offer to reply to for each worker
218-
val offerableWorkers = new ArrayBuffer[WorkerOffer]
219-
val offerableIndices = new HashMap[String, Int]
220-
221-
def sufficientOffer(o: Offer) = {
222-
val mem = getResource(o.getResourcesList, "mem")
223-
val cpus = getResource(o.getResourcesList, "cpus")
224-
val slaveId = o.getSlaveId.getValue
225-
(mem >= MemoryUtils.calculateTotalMemory(sc) &&
226-
// need at least 1 for executor, 1 for task
227-
cpus >= 2 * scheduler.CPUS_PER_TASK) ||
228-
(slaveIdsWithExecutors.contains(slaveId) &&
229-
cpus >= scheduler.CPUS_PER_TASK)
230-
}
231-
232-
for ((offer, index) <- offers.zipWithIndex if sufficientOffer(offer)) {
233-
val slaveId = offer.getSlaveId.getValue
234-
offerableIndices.put(slaveId, index)
235-
val cpus = if (slaveIdsWithExecutors.contains(slaveId)) {
236-
getResource(offer.getResourcesList, "cpus").toInt
237-
} else {
238-
// If the executor doesn't exist yet, subtract CPU for executor
239-
getResource(offer.getResourcesList, "cpus").toInt -
240-
scheduler.CPUS_PER_TASK
241-
}
242-
offerableWorkers += new WorkerOffer(
243-
offer.getSlaveId.getValue,
244-
offer.getHostname,
245-
cpus)
246-
}
247-
248-
// Call into the TaskSchedulerImpl
249-
val taskLists = scheduler.resourceOffers(offerableWorkers)
250-
251-
// Build a list of Mesos tasks for each slave
252-
val mesosTasks = offers.map(o => new JArrayList[MesosTaskInfo]())
253-
for ((taskList, index) <- taskLists.zipWithIndex) {
254-
if (!taskList.isEmpty) {
255-
for (taskDesc <- taskList) {
256-
val slaveId = taskDesc.executorId
257-
val offerNum = offerableIndices(slaveId)
258-
slaveIdsWithExecutors += slaveId
259-
taskIdToSlaveId(taskDesc.taskId) = slaveId
260-
mesosTasks(offerNum).add(createMesosTask(taskDesc, slaveId))
261-
}
262-
}
263-
}
264-
265-
// Reply to the offers
266-
val filters = Filters.newBuilder().setRefuseSeconds(1).build() // TODO: lower timeout?
267-
for (i <- 0 until offers.size) {
268-
d.launchTasks(Collections.singleton(offers(i).getId), mesosTasks(i), filters)
220+
val (acceptedOffers, declinedOffers) = offers.partition(o => {
221+
val mem = getResource(o.getResourcesList, "mem")
222+
val slaveId = o.getSlaveId.getValue
223+
mem >= sc.executorMemory || slaveIdsWithExecutors.contains(slaveId)
224+
})
225+
226+
val offerableWorkers = acceptedOffers.map(toWorkerOffer)
227+
228+
val slaveIdToOffer = acceptedOffers.map(o => o.getSlaveId.getValue -> o).toMap
229+
230+
val mesosTasks = new HashMap[String, JArrayList[MesosTaskInfo]]
231+
232+
// Call into the TaskSchedulerImpl
233+
scheduler.resourceOffers(offerableWorkers)
234+
.filter(!_.isEmpty)
235+
.foreach(_.foreach(taskDesc => {
236+
val slaveId = taskDesc.executorId
237+
slaveIdsWithExecutors += slaveId
238+
taskIdToSlaveId(taskDesc.taskId) = slaveId
239+
mesosTasks.getOrElseUpdate(slaveId, new JArrayList[MesosTaskInfo])
240+
.add(createMesosTask(taskDesc, slaveId))
241+
}))
242+
243+
// Reply to the offers
244+
val filters = Filters.newBuilder().setRefuseSeconds(1).build() // TODO: lower timeout?
245+
246+
mesosTasks.foreach {
247+
case (slaveId, tasks) => {
248+
d.launchTasks(Collections.singleton(slaveIdToOffer(slaveId).getId), tasks, filters)
269249
}
270250
}
251+
252+
declinedOffers.foreach(o => d.declineOffer(o.getId))
271253
} finally {
272254
restoreClassLoader(oldClassLoader)
273255
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.scheduler.mesos
19+
20+
import org.scalatest.FunSuite
21+
import org.apache.spark.{SparkConf, SparkContext, LocalSparkContext}
22+
import org.apache.spark.scheduler.{TaskDescription, WorkerOffer, TaskSchedulerImpl}
23+
import org.apache.spark.scheduler.cluster.mesos.MesosSchedulerBackend
24+
import org.apache.mesos.SchedulerDriver
25+
import org.apache.mesos.Protos._
26+
import org.scalatest.mock.EasyMockSugar
27+
import org.apache.mesos.Protos.Value.Scalar
28+
import org.easymock.{Capture, EasyMock}
29+
import java.nio.ByteBuffer
30+
import java.util.Collections
31+
import java.util
32+
33+
class MesosSchedulerBackendSuite extends FunSuite with LocalSparkContext with EasyMockSugar {
34+
test("mesos resource offer is launching tasks") {
35+
def createOffer(id: Int, mem: Int, cpu: Int) = {
36+
val builder = Offer.newBuilder()
37+
builder.addResourcesBuilder()
38+
.setName("mem")
39+
.setType(Value.Type.SCALAR)
40+
.setScalar(Scalar.newBuilder().setValue(mem))
41+
builder.addResourcesBuilder()
42+
.setName("cpus")
43+
.setType(Value.Type.SCALAR)
44+
.setScalar(Scalar.newBuilder().setValue(cpu))
45+
builder.setId(OfferID.newBuilder().setValue(id.toString).build()).setFrameworkId(FrameworkID.newBuilder().setValue("f1"))
46+
.setSlaveId(SlaveID.newBuilder().setValue("s1")).setHostname("localhost").build()
47+
}
48+
49+
val driver = EasyMock.createMock(classOf[SchedulerDriver])
50+
val taskScheduler = EasyMock.createMock(classOf[TaskSchedulerImpl])
51+
val offers = new java.util.ArrayList[Offer]
52+
offers.add(createOffer(1, 101, 1))
53+
offers.add(createOffer(1, 99, 1))
54+
55+
val conf = new SparkConf
56+
conf.set("spark.executor.memory", "100m")
57+
conf.set("spark.home", "/path")
58+
val sc = new SparkContext("local-cluster[2 , 1 , 512]", "test", conf)
59+
val backend = new MesosSchedulerBackend(taskScheduler, sc, "master")
60+
val workerOffers = Seq(backend.toWorkerOffer(offers.get(0)))
61+
val taskDesc = new TaskDescription(1L, "s1", "n1", 0, ByteBuffer.wrap(new Array[Byte](0)))
62+
EasyMock.expect(taskScheduler.resourceOffers(EasyMock.eq(workerOffers))).andReturn(Seq(Seq(taskDesc)))
63+
EasyMock.expect(taskScheduler.CPUS_PER_TASK).andReturn(2).anyTimes()
64+
EasyMock.replay(taskScheduler)
65+
val capture = new Capture[util.Collection[TaskInfo]]
66+
EasyMock.expect(
67+
driver.launchTasks(
68+
EasyMock.eq(Collections.singleton(offers.get(0).getId)),
69+
EasyMock.capture(capture),
70+
EasyMock.anyObject(classOf[Filters])
71+
)
72+
).andReturn(Status.valueOf(1))
73+
EasyMock.expect(driver.declineOffer(offers.get(1).getId)).andReturn(Status.valueOf(1))
74+
EasyMock.replay(driver)
75+
backend.resourceOffers(driver, offers)
76+
assert(capture.getValue.size() == 1)
77+
val taskInfo = capture.getValue.iterator().next()
78+
assert(taskInfo.getName.equals("n1"))
79+
val cpus = taskInfo.getResourcesList.get(0)
80+
assert(cpus.getName.equals("cpus"))
81+
assert(cpus.getScalar.getValue.equals(2.0))
82+
assert(taskInfo.getSlaveId.getValue.equals("s1"))
83+
}
84+
}

0 commit comments

Comments
 (0)