You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ji...@apache.org on 2014/07/16 18:34:44 UTC

git commit: Added a low level scheduler example using pthread.

Repository: mesos
Updated Branches:
  refs/heads/master 11b6fd851 -> 049aed3c9


Added a low level scheduler example using pthread.

Review: https://reviews.apache.org/r/23216


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/049aed3c
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/049aed3c
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/049aed3c

Branch: refs/heads/master
Commit: 049aed3c9c19405dade552f762b2741935223a16
Parents: 11b6fd8
Author: Zuyu Zhang <hi...@gmail.com>
Authored: Wed Jul 16 09:34:25 2014 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Wed Jul 16 09:34:25 2014 -0700

----------------------------------------------------------------------
 src/Makefile.am                                 |   6 +
 src/examples/low_level_scheduler_libprocess.cpp |  45 +-
 src/examples/low_level_scheduler_pthread.cpp    | 486 +++++++++++++++++++
 src/tests/examples_tests.cpp                    |   2 +
 src/tests/low_level_scheduler_pthread_test.sh   |  28 ++
 5 files changed, 542 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/049aed3c/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index a1fe9dc..c216e2f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -966,6 +966,11 @@ low_level_scheduler_libprocess_SOURCES = examples/low_level_scheduler_libprocess
 low_level_scheduler_libprocess_CPPFLAGS = $(MESOS_CPPFLAGS)
 low_level_scheduler_libprocess_LDADD = libmesos.la
 
+check_PROGRAMS += low-level-scheduler-pthread
+low_level_scheduler_pthread_SOURCES = examples/low_level_scheduler_pthread.cpp
+low_level_scheduler_pthread_CPPFLAGS = $(MESOS_CPPFLAGS)
+low_level_scheduler_pthread_LDADD = libmesos.la
+
 check_PROGRAMS += test-framework
 test_framework_SOURCES = examples/test_framework.cpp
 test_framework_CPPFLAGS = $(MESOS_CPPFLAGS)
@@ -1114,6 +1119,7 @@ EXTRA_DIST += examples/python/test_containerizer.py			\
 dist_check_SCRIPTS +=							\
   tests/balloon_framework_test.sh					\
   tests/low_level_scheduler_libprocess_test.sh						\
+  tests/low_level_scheduler_pthread_test.sh						\
   tests/test_framework_test.sh						\
   tests/no_executor_framework_test.sh					\
   tests/java_exception_test.sh						\

http://git-wip-us.apache.org/repos/asf/mesos/blob/049aed3c/src/examples/low_level_scheduler_libprocess.cpp
----------------------------------------------------------------------
diff --git a/src/examples/low_level_scheduler_libprocess.cpp b/src/examples/low_level_scheduler_libprocess.cpp
index 784f25e..83fe815 100644
--- a/src/examples/low_level_scheduler_libprocess.cpp
+++ b/src/examples/low_level_scheduler_libprocess.cpp
@@ -67,10 +67,10 @@ class LowLevelScheduler : public process::Process<LowLevelScheduler>
 public:
   LowLevelScheduler(const FrameworkInfo& _framework,
                     const ExecutorInfo& _executor,
-                    const string& _master)
+                    const string& master)
     : framework(_framework),
       executor(_executor),
-      mesos(_master,
+      mesos(master,
             process::defer(self(), &Self::connected),
             process::defer(self(), &Self::disconnected),
             process::defer(self(), &Self::received, lambda::_1)),
@@ -81,11 +81,11 @@ public:
 
   LowLevelScheduler(const FrameworkInfo& _framework,
                     const ExecutorInfo& _executor,
-                    const string& _master,
+                    const string& master,
                     const Credential& credential)
     : framework(_framework),
       executor(_executor),
-      mesos(_master,
+      mesos(master,
             credential,
             process::defer(self(), &Self::connected),
             process::defer(self(), &Self::disconnected),
@@ -117,8 +117,7 @@ public:
         case Event::REGISTERED: {
           cout << endl << "Received a REGISTERED event" << endl;
 
-          framework.mutable_id()->CopyFrom(
-              event.registered().framework_id());
+          framework.mutable_id()->CopyFrom(event.registered().framework_id());
           state = REGISTERED;
 
           cout << "Framework '" << event.registered().framework_id().value()
@@ -166,7 +165,7 @@ public:
           cout << endl << "Received a FAILURE event" << endl;
 
           if (event.failure().has_executor_id()) {
-            // Executor failure.
+            // Executor failed.
             cout << "Executor '"
                  << event.failure().executor_id().value() << "' terminated";
 
@@ -178,16 +177,14 @@ public:
             if (event.failure().has_status()) {
               int status = event.failure().status();
               if (WIFEXITED(status)) {
-                cout << ", and exited with status "
-                     << WEXITSTATUS(status);
+                cout << ", and exited with status " << WEXITSTATUS(status);
               } else {
-                cout << ", and terminated with signal "
-                     << WTERMSIG(status);
+                cout << ", and terminated with signal " << WTERMSIG(status);
               }
             }
             cout << endl;
           } else if (event.failure().has_slave_id()) {
-            // Slave failure.
+            // Slave failed.
             cout << "Slave '" << event.failure().slave_id().value()
                  << "' terminated" << endl;
           }
@@ -195,8 +192,8 @@ public:
         }
 
         case Event::ERROR: {
-          cout << endl << "Received an ERROR event" << endl;
-          cout << event.error().message() << endl;
+          cout << endl << "Received an ERROR event: "
+               << event.error().message() << endl;
           process::terminate(self());
           break;
         }
@@ -299,12 +296,8 @@ private:
 
     Call call;
     call.mutable_framework_info()->CopyFrom(framework);
-
-    if (state == INITIALIZING) {
-      call.set_type(Call::REGISTER);
-    } else {  // state is DISCONNECTED.
-      call.set_type(Call::REREGISTER);
-    }
+    call.set_type(
+        state == INITIALIZING ? Call::REGISTER : Call::REREGISTER);
 
     mesos.send(call);
 
@@ -337,6 +330,7 @@ private:
   const int totalTasks;
 };
 
+
 void usage(const char* argv0, const flags::FlagsBase& flags)
 {
   cerr << "Usage: " << os::basename(argv0).get() << " [...]" << endl
@@ -345,13 +339,14 @@ void usage(const char* argv0, const flags::FlagsBase& flags)
        << flags.usage();
 }
 
+
 int main(int argc, char** argv)
 {
   // Find this executable's directory to locate executor.
-  string path = os::realpath(dirname(argv[0])).get();
-  string uri = path + "/test-executor";
+  string uri =
+    path::join(os::realpath(dirname(argv[0])).get(), "src", "test-executor");
   if (getenv("MESOS_BUILD_DIR")) {
-    uri = string(getenv("MESOS_BUILD_DIR")) + "/src/test-executor";
+    uri = path::join(os::getenv("MESOS_BUILD_DIR"), "src", "test-executor");
   }
 
   mesos::internal::logging::Flags flags;
@@ -372,11 +367,11 @@ int main(int argc, char** argv)
   if (load.isError()) {
     cerr << load.error() << endl;
     usage(argv[0], flags);
-    exit(1);
+    EXIT(1);
   } else if (master.isNone()) {
     cerr << "Missing --master" << endl;
     usage(argv[0], flags);
-    exit(1);
+    EXIT(1);
   }
 
   FrameworkInfo framework;

http://git-wip-us.apache.org/repos/asf/mesos/blob/049aed3c/src/examples/low_level_scheduler_pthread.cpp
----------------------------------------------------------------------
diff --git a/src/examples/low_level_scheduler_pthread.cpp b/src/examples/low_level_scheduler_pthread.cpp
new file mode 100644
index 0000000..1502247
--- /dev/null
+++ b/src/examples/low_level_scheduler_pthread.cpp
@@ -0,0 +1,486 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <libgen.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <string>
+#include <queue>
+
+#include <boost/lexical_cast.hpp>
+
+#include <process/protobuf.hpp>
+
+#include <mesos/resources.hpp>
+#include <mesos/scheduler.hpp>
+
+#include <stout/check.hpp>
+#include <stout/exit.hpp>
+#include <stout/flags.hpp>
+#include <stout/foreach.hpp>
+#include <stout/lambda.hpp>
+#include <stout/none.hpp>
+#include <stout/option.hpp>
+#include <stout/os.hpp>
+#include <stout/stringify.hpp>
+
+#include "logging/flags.hpp"
+
+using namespace mesos;
+
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::queue;
+using std::string;
+using std::vector;
+
+using boost::lexical_cast;
+
+using mesos::Resources;
+using mesos::scheduler::Call;
+using mesos::scheduler::Event;
+
+const int32_t CPUS_PER_TASK = 1;
+const int32_t MEM_PER_TASK = 32;
+
+class LowLevelScheduler
+{
+public:
+  LowLevelScheduler(const FrameworkInfo& _framework,
+                    const ExecutorInfo& _executor,
+                    const string& master)
+    : framework(_framework),
+      executor(_executor),
+      mesos(master,
+            lambda::bind(&LowLevelScheduler::connected, this),
+            lambda::bind(&LowLevelScheduler::disconnected, this),
+            lambda::bind(&LowLevelScheduler::received, this, lambda::_1)),
+      state(INITIALIZING),
+      tasksLaunched(0),
+      tasksFinished(0),
+      totalTasks(5)
+  {
+    pthread_mutex_init(&mutex, NULL);
+    pthread_cond_init(&cond, 0);
+  }
+
+  LowLevelScheduler(const FrameworkInfo& _framework,
+                    const ExecutorInfo& _executor,
+                    const string& master,
+                    const Credential& credential)
+    : framework(_framework),
+      executor(_executor),
+      mesos(master,
+            credential,
+            lambda::bind(&LowLevelScheduler::connected, this),
+            lambda::bind(&LowLevelScheduler::disconnected, this),
+            lambda::bind(&LowLevelScheduler::received, this, lambda::_1)),
+      state(INITIALIZING),
+      tasksLaunched(0),
+      tasksFinished(0),
+      totalTasks(5)
+  {
+    pthread_mutex_init(&mutex, NULL);
+    pthread_cond_init(&cond, 0);
+  }
+
+  ~LowLevelScheduler()
+  {
+    pthread_mutex_destroy(&mutex);
+    pthread_cond_destroy(&cond);
+  }
+
+  void connected(void)
+  {
+    pthread_mutex_lock(&mutex);
+    state = CONNECTED;
+
+    // Unblock the main thread calling wait().
+    pthread_cond_signal(&cond);
+    pthread_mutex_unlock(&mutex);
+  }
+
+  void disconnected(void)
+  {
+    pthread_mutex_lock(&mutex);
+    state = DISCONNECTED;
+    pthread_mutex_unlock(&mutex);
+  }
+
+  void received(queue<Event> events)
+  {
+    while (!events.empty()) {
+      Event event = events.front();
+      events.pop();
+
+      switch (event.type()) {
+        case Event::REGISTERED: {
+          cout << endl << "Received a REGISTERED event" << endl;
+
+          pthread_mutex_lock(&mutex);
+          state = REGISTERED;
+          pthread_mutex_unlock(&mutex);
+
+          framework.mutable_id()->CopyFrom(event.registered().framework_id());
+
+          cout << "Framework '" << event.registered().framework_id().value()
+               << "' registered with Master '"
+               << event.registered().master_info().id() << "'" << endl;
+          break;
+        }
+
+        case Event::REREGISTERED: {
+          cout << endl << "Received a REREGISTERED event" << endl;
+
+          pthread_mutex_lock(&mutex);
+          state = REGISTERED;
+          pthread_mutex_unlock(&mutex);
+
+          cout << "Framework '" << event.reregistered().framework_id().value()
+               << "' re-registered with Master '"
+               << event.reregistered().master_info().id() << "'" << endl;
+          break;
+        }
+
+        case Event::OFFERS: {
+          cout << endl << "Received an OFFERS event" << endl;
+          resourceOffers(google::protobuf::convert(event.offers().offers()));
+          break;
+        }
+
+        case Event::RESCIND: {
+          cout << endl << "Received a RESCIND event" << endl;
+          break;
+        }
+
+        case Event::UPDATE: {
+          cout << endl << "Received an UPDATE event" << endl;
+
+          // TODO(zuyu): Do batch processing of UPDATE events.
+          statusUpdate(event.update().uuid(), event.update().status());
+          break;
+        }
+
+        case Event::MESSAGE: {
+          cout << endl << "Received a MESSAGE event" << endl;
+          break;
+        }
+
+        case Event::FAILURE: {
+          cout << endl << "Received a FAILURE event" << endl;
+
+          if (event.failure().has_executor_id()) {
+            // Executor failed.
+            cout << "Executor '"
+                 << event.failure().executor_id().value() << "' terminated";
+
+            if (event.failure().has_slave_id()) {
+              cout << " on Slave '"
+                   << event.failure().slave_id().value() << "'";
+            }
+
+            if (event.failure().has_status()) {
+              int status = event.failure().status();
+              if (WIFEXITED(status)) {
+                cout << ", and exited with status " << WEXITSTATUS(status);
+              } else {
+                cout << ", and terminated with signal " << WTERMSIG(status);
+              }
+            }
+            cout << endl;
+          } else {
+            // Slave failed.
+            cout << "Slave '" << event.failure().slave_id().value()
+                 << "' terminated" << endl;
+          }
+          break;
+        }
+
+        case Event::ERROR: {
+          cout << endl << "Received an ERROR event: "
+               << event.error().message() << endl;
+          finalize();
+          break;
+        }
+
+        default: {
+          EXIT(1) << "Received an UNKNOWN event";
+        }
+      }
+    }
+  }
+
+  void wait(void)
+  {
+    pthread_mutex_lock(&mutex);
+    if (state == INITIALIZING) {
+      // wait for connected() to be called.
+      pthread_cond_wait(&cond, &mutex);
+    }
+
+    // CONNECTED state.
+    pthread_mutex_unlock(&mutex);
+
+    while (true) {
+      pthread_mutex_lock(&mutex);
+      if (state == CONNECTED ||
+          state == DISCONNECTED) {
+        pthread_mutex_unlock(&mutex);
+        doRegistration();
+        sleep(1);
+      } else if (state == DONE) {
+        pthread_mutex_unlock(&mutex);
+        break;
+      } else {
+        pthread_cond_wait(&cond, &mutex);
+        pthread_mutex_unlock(&mutex);
+      }
+    }
+  }
+
+private:
+  void resourceOffers(const vector<Offer>& offers)
+  {
+    foreach (const Offer& offer, offers) {
+      cout << "Offer '" << offer.id().value() << "' has "
+           << offer.resources() << endl;
+
+      static const Resources TASK_RESOURCES = Resources::parse(
+          "cpus:" + stringify(CPUS_PER_TASK) +
+          ";mem:" + stringify(MEM_PER_TASK)).get();
+
+      Resources remaining = offer.resources();
+
+      // Launch tasks.
+      vector<TaskInfo> tasks;
+      while (tasksLaunched < totalTasks &&
+             TASK_RESOURCES <= remaining.flatten()) {
+        int taskId = tasksLaunched++;
+
+        cout << "Starting task " << taskId << " on "
+             << offer.hostname() << endl;
+
+        TaskInfo task;
+        task.set_name("Task " + lexical_cast<string>(taskId));
+        task.mutable_task_id()->set_value(lexical_cast<string>(taskId));
+        task.mutable_slave_id()->MergeFrom(offer.slave_id());
+        task.mutable_executor()->MergeFrom(executor);
+
+        Option<Resources> resources =
+          remaining.find(TASK_RESOURCES, framework.role());
+
+        CHECK_SOME(resources);
+        task.mutable_resources()->MergeFrom(resources.get());
+        remaining -= resources.get();
+
+        tasks.push_back(task);
+      }
+
+      Call call;
+      call.set_type(Call::LAUNCH);
+      call.mutable_framework_info()->CopyFrom(framework);
+
+      Call::Launch* launch = call.mutable_launch();
+      foreach (const TaskInfo& taskInfo, tasks) {
+        launch->add_task_infos()->CopyFrom(taskInfo);
+      }
+      launch->add_offer_ids()->CopyFrom(offer.id());
+
+      mesos.send(call);
+    }
+  }
+
+  void statusUpdate(const string& uuid, const TaskStatus& status)
+  {
+    cout << "Task " << status.task_id().value() << " is in state "
+         << TaskState_Name(status.state());
+
+    if (status.has_message()) {
+      cout << " with message '" << status.message() << "'";
+    }
+    cout << endl;
+
+    Call call;
+    call.set_type(Call::ACKNOWLEDGE);
+    call.mutable_framework_info()->CopyFrom(framework);
+
+    Call::Acknowledge* ack = call.mutable_acknowledge();
+    ack->mutable_slave_id()->CopyFrom(status.slave_id());
+    ack->mutable_task_id ()->CopyFrom(status.task_id ());
+    ack->set_uuid(uuid);
+
+    mesos.send(call);
+
+    if (status.state() == TASK_FINISHED) {
+      ++tasksFinished;
+    }
+
+    if (tasksFinished == totalTasks) {
+      finalize();
+    }
+  }
+
+  void doRegistration()
+  {
+    pthread_mutex_lock(&mutex);
+    if (state != CONNECTED &&
+        state != DISCONNECTED) {
+      pthread_mutex_unlock(&mutex);
+      return;
+    }
+
+    Call call;
+    call.mutable_framework_info()->CopyFrom(framework);
+    call.set_type(
+        state == CONNECTED ? Call::REGISTER : Call::REREGISTER);
+    pthread_mutex_unlock(&mutex);
+
+    mesos.send(call);
+  }
+
+  void finalize(void)
+  {
+    Call call;
+    call.set_type(Call::UNREGISTER);
+    call.mutable_framework_info()->CopyFrom(framework);
+
+    mesos.send(call);
+
+    pthread_mutex_lock(&mutex);
+    state = DONE;
+    pthread_cond_signal(&cond);
+    pthread_mutex_unlock(&mutex);
+  }
+
+  FrameworkInfo framework;
+  const ExecutorInfo executor;
+  scheduler::Mesos mesos;
+
+  enum State {
+    INITIALIZING = 0,
+    CONNECTED = 1,
+    REGISTERED = 2,
+    DISCONNECTED = 3,
+    DONE = 4
+  } state;
+
+  int tasksLaunched;
+  int tasksFinished;
+  const int totalTasks;
+
+  pthread_mutex_t mutex;
+  pthread_cond_t cond;
+};
+
+
+void usage(const char* argv0, const flags::FlagsBase& flags)
+{
+  cerr << "Usage: " << os::basename(argv0).get() << " [...]" << endl
+       << endl
+       << "Supported options:" << endl
+       << flags.usage();
+}
+
+
+int main(int argc, char** argv)
+{
+  // Find this executable's directory to locate executor.
+  string uri =
+    path::join(os::realpath(dirname(argv[0])).get(), "src", "test-executor");
+  if (getenv("MESOS_BUILD_DIR")) {
+    uri = path::join(os::getenv("MESOS_BUILD_DIR"), "src", "test-executor");
+  }
+
+  mesos::internal::logging::Flags flags;
+
+  string role;
+  flags.add(&role,
+            "role",
+            "Role to use when registering",
+            "*");
+
+  Option<string> master;
+  flags.add(&master,
+            "master",
+            "ip:port of master to connect");
+
+  Try<Nothing> load = flags.load(None(), argc, argv);
+
+  if (load.isError()) {
+    cerr << load.error() << endl;
+    usage(argv[0], flags);
+    EXIT(1);
+  } else if (master.isNone()) {
+    cerr << "Missing --master" << endl;
+    usage(argv[0], flags);
+    EXIT(1);
+  }
+
+  FrameworkInfo framework;
+  framework.set_user(""); // Have Mesos fill in the current user.
+  framework.set_name("Low-Level Scheduler using pthread (C++)");
+  framework.set_role(role);
+
+  // TODO(vinod): Make checkpointing the default when it is default
+  // on the slave.
+  if (os::hasenv("MESOS_CHECKPOINT")) {
+    cout << "Enabling checkpoint for the framework" << endl;
+    framework.set_checkpoint(true);
+  }
+
+  ExecutorInfo executor;
+  executor.mutable_executor_id()->set_value("default");
+  executor.mutable_command()->set_value(uri);
+  executor.set_name("Test Executor (C++)");
+  executor.set_source("cpp_test");
+
+  LowLevelScheduler* scheduler;
+  if (os::hasenv("MESOS_AUTHENTICATE")) {
+    cout << "Enabling authentication for the scheduler" << endl;
+
+    if (!os::hasenv("DEFAULT_PRINCIPAL")) {
+      EXIT(1) << "Expecting authentication principal in the environment";
+    }
+
+    if (!os::hasenv("DEFAULT_SECRET")) {
+      EXIT(1) << "Expecting authentication secret in the environment";
+    }
+
+    Credential credential;
+    credential.set_principal(getenv("DEFAULT_PRINCIPAL"));
+    credential.set_secret(getenv("DEFAULT_SECRET"));
+
+    framework.set_principal(getenv("DEFAULT_PRINCIPAL"));
+
+    scheduler =
+      new LowLevelScheduler(framework, executor, master.get(), credential);
+  } else {
+    framework.set_principal("low-level-scheduler-pthread-cpp");
+
+    scheduler =
+      new LowLevelScheduler(framework, executor, master.get());
+  }
+
+  scheduler->wait();
+  delete scheduler;
+
+  return 0;
+}

http://git-wip-us.apache.org/repos/asf/mesos/blob/049aed3c/src/tests/examples_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/examples_tests.cpp b/src/tests/examples_tests.cpp
index 2b554d7..4ee0b30 100644
--- a/src/tests/examples_tests.cpp
+++ b/src/tests/examples_tests.cpp
@@ -26,6 +26,8 @@ TEST_SCRIPT(ExamplesTest, TestFramework, "test_framework_test.sh")
 TEST_SCRIPT(ExamplesTest, NoExecutorFramework, "no_executor_framework_test.sh")
 TEST_SCRIPT(ExamplesTest, LowLevelSchedulerLibprocess,
             "low_level_scheduler_libprocess_test.sh")
+TEST_SCRIPT(ExamplesTest, LowLevelSchedulerPthread,
+            "low_level_scheduler_pthread_test.sh")
 
 #ifdef MESOS_HAS_JAVA
 TEST_SCRIPT(ExamplesTest, JavaFramework, "java_framework_test.sh")

http://git-wip-us.apache.org/repos/asf/mesos/blob/049aed3c/src/tests/low_level_scheduler_pthread_test.sh
----------------------------------------------------------------------
diff --git a/src/tests/low_level_scheduler_pthread_test.sh b/src/tests/low_level_scheduler_pthread_test.sh
new file mode 100755
index 0000000..839e8eb
--- /dev/null
+++ b/src/tests/low_level_scheduler_pthread_test.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+# Expecting MESOS_SOURCE_DIR and MESOS_BUILD_DIR to be in environment.
+
+env | grep MESOS_SOURCE_DIR >/dev/null
+
+test $? != 0 && \
+  echo "Failed to find MESOS_SOURCE_DIR in environment" && \
+  exit 1
+
+env | grep MESOS_BUILD_DIR >/dev/null
+
+test $? != 0 && \
+  echo "Failed to find MESOS_BUILD_DIR in environment" && \
+  exit 1
+
+source ${MESOS_SOURCE_DIR}/support/atexit.sh
+
+MESOS_WORK_DIR=`mktemp -d -t mesos-XXXXXX`
+
+atexit "rm -rf ${MESOS_WORK_DIR}"
+export MESOS_WORK_DIR=${MESOS_WORK_DIR}
+
+# Set local Mesos runner to use 3 slaves
+export MESOS_NUM_SLAVES=3
+
+# Check that the C++ low level scheduler executes without crashing (returns 0).
+exec ${MESOS_BUILD_DIR}/src/low-level-scheduler-pthread --master=local