From 7b848c1a08ae66ed8d1f2815b3b8e5720572e332 Mon Sep 17 00:00:00 2001 From: "Tony L. Kerz" Date: Sat, 9 Jan 2016 09:38:37 -0500 Subject: [PATCH] [constraints] by hostname --- docs/docs/api.md | 5 +- .../scheduler/mesos/ConstraintChecker.scala | 28 +++++++ .../scheduler/mesos/MesosJobFramework.scala | 11 +-- .../mesos/ConstraintCheckerSpec.scala | 76 +++++++++++++++++++ 4 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 src/main/scala/org/apache/mesos/chronos/scheduler/mesos/ConstraintChecker.scala create mode 100644 src/test/scala/org/apache/mesos/chronos/scheduler/mesos/ConstraintCheckerSpec.scala diff --git a/docs/docs/api.md b/docs/docs/api.md index d2ef6ad4d..f5f170144 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -387,8 +387,9 @@ When specifying the `command` field in your job hash, use `url-runner.bash` (mak ## Constraints -These constraints will currently only work against attributes that are specifically set on the Mesos slaves [as described in the Mesos documentation](http://mesos.apache.org/documentation/latest/configuration). -(i.e. the `hostname` attribute is not currently automatically available for constraints [as it is in Marathon](https://mesosphere.github.io/marathon/docs/constraints)) +These constraints will work against attributes that are specifically set on the Mesos slaves [as described in the Mesos documentation](http://mesos.apache.org/documentation/latest/configuration). + +If a `hostname` attribute is not explicitly specified, one will automatically be created and made available for constraints. It should be noted that calling out specific hostnames is not resilient to slave failure and should be avoided if possible. ### EQUALS constraint diff --git a/src/main/scala/org/apache/mesos/chronos/scheduler/mesos/ConstraintChecker.scala b/src/main/scala/org/apache/mesos/chronos/scheduler/mesos/ConstraintChecker.scala new file mode 100644 index 000000000..621bde4f8 --- /dev/null +++ b/src/main/scala/org/apache/mesos/chronos/scheduler/mesos/ConstraintChecker.scala @@ -0,0 +1,28 @@ +package org.apache.mesos.chronos.scheduler.mesos + +import org.apache.mesos.Protos +import org.apache.mesos.chronos.scheduler.jobs.constraints.Constraint +import java.util.logging.Logger +import scala.collection.JavaConverters._ + +/** + * Helper for checking resource offer against job constraints + */ +object ConstraintChecker { + private[this] val log = Logger.getLogger(getClass.getName) + val Hostname = "hostname" + + def checkConstraints(offer: Protos.Offer, constraints: Seq[Constraint]): Boolean = { + var attributes = offer.getAttributesList.asScala + + if (!attributes.exists(attr => attr.getName == Hostname)) { + log.fine(s"adding hostname-attribute=${offer.getHostname} to offer=${offer}") + val hostnameText = Protos.Value.Text.newBuilder().setValue(offer.getHostname).build() + val hostnameAttribute = Protos.Attribute.newBuilder().setName(Hostname).setText(hostnameText).setType(Protos.Value.Type.TEXT).build() + attributes = offer.getAttributesList.asScala :+ hostnameAttribute + } + + constraints.forall(_.matches(attributes)) + } + +} diff --git a/src/main/scala/org/apache/mesos/chronos/scheduler/mesos/MesosJobFramework.scala b/src/main/scala/org/apache/mesos/chronos/scheduler/mesos/MesosJobFramework.scala index 008308782..6e54dada8 100644 --- a/src/main/scala/org/apache/mesos/chronos/scheduler/mesos/MesosJobFramework.scala +++ b/src/main/scala/org/apache/mesos/chronos/scheduler/mesos/MesosJobFramework.scala @@ -116,15 +116,6 @@ class MesosJobFramework @Inject()( def generateLaunchableTasks(offerResources: mutable.HashMap[Offer, Resources]): mutable.Buffer[(String, BaseJob, Offer)] = { val tasks = mutable.Buffer[(String, BaseJob, Offer)]() - def checkConstraints(attributes: Seq[Protos.Attribute], constraints: Seq[Constraint]): Boolean = { - constraints.foreach { c => - if (!c.matches(attributes)) { - return false - } - } - true - } - @tailrec def generate() { taskManager.getTask match { @@ -143,7 +134,7 @@ class MesosJobFramework @Inject()( case None => val neededResources = new Resources(job) offerResources.toIterator.find { ors => - ors._2.canSatisfy(neededResources) && checkConstraints(ors._1.getAttributesList.asScala, job.constraints) + ors._2.canSatisfy(neededResources) && ConstraintChecker.checkConstraints(ors._1, job.constraints) } match { case Some((offer, resources)) => // Subtract this job's resource requirements from the remaining available resources in this offer. diff --git a/src/test/scala/org/apache/mesos/chronos/scheduler/mesos/ConstraintCheckerSpec.scala b/src/test/scala/org/apache/mesos/chronos/scheduler/mesos/ConstraintCheckerSpec.scala new file mode 100644 index 000000000..b858b047b --- /dev/null +++ b/src/test/scala/org/apache/mesos/chronos/scheduler/mesos/ConstraintCheckerSpec.scala @@ -0,0 +1,76 @@ +package org.apache.mesos.chronos.scheduler.mesos + +import mesosphere.mesos.protos._ +import org.apache.mesos.chronos.scheduler.jobs.constraints.ConstraintSpecHelper +import org.apache.mesos.Protos +import java.util.logging.Logger +import org.specs2.mock.Mockito +import org.specs2.mutable.SpecificationWithJUnit +import org.apache.mesos.chronos.scheduler.jobs.constraints.LikeConstraint +import org.apache.mesos.chronos.scheduler.jobs.constraints.EqualsConstraint +import mesosphere.mesos.protos.Implicits._ +import org.specs2.specification.BeforeEach + +class ConstraintCheckerSpec extends SpecificationWithJUnit +with Mockito +with ConstraintSpecHelper { + + val offer = Protos.Offer.newBuilder() + .setId(OfferID("1")) + .setFrameworkId(FrameworkID("chronos")) + .setSlaveId(SlaveID("slave-1")) + .setHostname("slave.one.com") + .addAttributes(createTextAttribute("rack", "rack-1")) + .build() + + val offerWithHostname = Protos.Offer.newBuilder() + .setId(OfferID("1")) + .setFrameworkId(FrameworkID("chronos")) + .setSlaveId(SlaveID("slave-1")) + .setHostname("slave.one.com") + .addAttributes(createTextAttribute("hostname", "slave.explicit.com")) + .build() + + "check constraints" should { + + "be true when equal" in { + val constraints = Seq(EqualsConstraint("rack", "rack-1")) + ConstraintChecker.checkConstraints(offer, constraints) must beTrue + } + + "be false when not equal" in { + val constraints = Seq(EqualsConstraint("rack", "rack-2")) + ConstraintChecker.checkConstraints(offer, constraints) must beFalse + } + + "be true when like" in { + val constraints = Seq(LikeConstraint("rack", "rack-[1-3]")) + ConstraintChecker.checkConstraints(offer, constraints) must beTrue + } + + "be false when not like" in { + val constraints = Seq(LikeConstraint("rack", "rack-[2-3]")) + ConstraintChecker.checkConstraints(offer, constraints) must beFalse + } + + "be true when hostname equal" in { + val constraints = Seq(EqualsConstraint("hostname", "slave.one.com")) + ConstraintChecker.checkConstraints(offer, constraints) must beTrue + } + + "be false when hostname not equal" in { + val constraints = Seq(EqualsConstraint("hostname", "slave.two.com")) + ConstraintChecker.checkConstraints(offer, constraints) must beFalse + } + + "be false when hostname explicitly set to something else and not equal" in { + val constraints = Seq(EqualsConstraint("hostname", "slave.one.com")) + ConstraintChecker.checkConstraints(offerWithHostname, constraints) must beFalse + } + + "be true when hostname explicitly set to something else and equal" in { + val constraints = Seq(EqualsConstraint("hostname", "slave.explicit.com")) + ConstraintChecker.checkConstraints(offerWithHostname, constraints) must beTrue + } + } +}