You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by be...@apache.org on 2013/10/25 17:51:25 UTC
[5/5] git commit: Made 'mesos' be a "subcommand dispatcher" and added
the 'resolve', 'execute' and 'ps' subcommands.
Made 'mesos' be a "subcommand dispatcher" and added the 'resolve',
'execute' and 'ps' subcommands.
Review: https://reviews.apache.org/r/14816
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/5141dd67
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/5141dd67
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/5141dd67
Branch: refs/heads/master
Commit: 5141dd67eeffb4f8de42c18a399b954ef331dd1b
Parents: 2c75189
Author: Benjamin Hindman <be...@gmail.com>
Authored: Sat Oct 12 22:34:29 2013 -0700
Committer: Benjamin Hindman <be...@gmail.com>
Committed: Thu Oct 24 22:46:27 2013 -0700
----------------------------------------------------------------------
bin/mesos.sh.in | 30 +++++
configure.ac | 1 +
src/Makefile.am | 15 +++
src/cli/execute.cpp | 327 +++++++++++++++++++++++++++++++++++++++++++++++
src/cli/mesos-ps | 229 +++++++++++++++++++++++++++++++++
src/cli/mesos.cpp | 98 +++++++++++++-
src/cli/resolve.cpp | 135 +++++++++++++++++++
7 files changed, 833 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/5141dd67/bin/mesos.sh.in
----------------------------------------------------------------------
diff --git a/bin/mesos.sh.in b/bin/mesos.sh.in
new file mode 100644
index 0000000..3d03ee4
--- /dev/null
+++ b/bin/mesos.sh.in
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+# 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.
+
+# This is a wrapper for running mesos-tests from the build directory
+# that first sets up environment variables as appropriate.
+
+# Add any scripts from 'src/cli'.
+PATH=@abs_top_srcdir@/src/cli:${PATH}
+
+# Add the executables (or rather, their libtool wrappers) from 'src'.
+PATH=@abs_top_builddir@/src:${PATH}
+
+export PATH
+
+exec @abs_top_builddir@/src/mesos "${@}"
http://git-wip-us.apache.org/repos/asf/mesos/blob/5141dd67/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 831784b..77b5a3d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -81,6 +81,7 @@ AC_CONFIG_FILES([ec2/Makefile])
AC_CONFIG_FILES([src/Makefile])
AC_CONFIG_FILES([3rdparty/Makefile])
+AC_CONFIG_FILES([bin/mesos.sh], [chmod +x bin/mesos.sh])
AC_CONFIG_FILES([bin/mesos-local.sh], [chmod +x bin/mesos-local.sh])
AC_CONFIG_FILES([bin/mesos-master.sh], [chmod +x bin/mesos-master.sh])
AC_CONFIG_FILES([bin/mesos-slave.sh], [chmod +x bin/mesos-slave.sh])
http://git-wip-us.apache.org/repos/asf/mesos/blob/5141dd67/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 6c88dc1..a16e8f8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -52,6 +52,7 @@ noinst_LTLIBRARIES =
sbin_PROGRAMS =
bin_PROGRAMS =
pkglibexec_PROGRAMS =
+dist_bin_SCRIPTS =
dist_pkglibexec_SCRIPTS =
nobase_dist_pkgdata_DATA =
nodist_sbin_SCRIPTS =
@@ -362,6 +363,20 @@ mesos_SOURCES = cli/mesos.cpp
mesos_CPPFLAGS = $(MESOS_CPPFLAGS)
mesos_LDADD = libmesos.la
+bin_PROGRAMS += mesos-execute
+mesos_execute_SOURCES = cli/execute.cpp
+mesos_execute_CPPFLAGS = $(MESOS_CPPFLAGS)
+mesos_execute_LDADD = libmesos.la
+
+bin_PROGRAMS += mesos-resolve
+mesos_resolve_SOURCES = cli/resolve.cpp
+mesos_resolve_CPPFLAGS = $(MESOS_CPPFLAGS)
+mesos_resolve_LDADD = libmesos.la
+
+# Need to distribute and install mesos-*.
+dist_bin_SCRIPTS += \
+ cli/mesos-ps
+
# Need to distribute/install webui javascript. We use 'pkgdatadir'
# instead of 'datadir' as the install directory so we get the the
# package name (i.e., 'mesos') as part of the path (i.e.,
http://git-wip-us.apache.org/repos/asf/mesos/blob/5141dd67/src/cli/execute.cpp
----------------------------------------------------------------------
diff --git a/src/cli/execute.cpp b/src/cli/execute.cpp
new file mode 100644
index 0000000..ac2f299
--- /dev/null
+++ b/src/cli/execute.cpp
@@ -0,0 +1,327 @@
+/**
+ * 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 <iostream>
+#include <vector>
+
+#include <mesos/resources.hpp>
+#include <mesos/scheduler.hpp>
+
+#include <process/pid.hpp>
+
+#include <stout/check.hpp>
+#include <stout/flags.hpp>
+#include <stout/none.hpp>
+#include <stout/option.hpp>
+#include <stout/os.hpp>
+
+#include "hdfs/hdfs.hpp"
+
+using namespace mesos;
+using namespace mesos::internal;
+
+using process::UPID;
+
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::string;
+using std::vector;
+
+
+void usage(const char* argv0, const flags::FlagsBase& flags)
+{
+ cerr << "Usage: " << os::basename(argv0).get() << " [...]" << endl
+ << endl
+ << "Supported options:" << endl
+ << flags.usage();
+}
+
+
+class Flags : public flags::FlagsBase
+{
+public:
+ Flags()
+ {
+ add(&master,
+ "master",
+ "Mesos master (e.g., IP1:PORT1)");
+
+ add(&name,
+ "name",
+ "Name for the command");
+
+ add(&command,
+ "command",
+ "Shell command to launch");
+
+ add(&resources,
+ "resources",
+ "Resources for the command",
+ "cpus:1;mem:128");
+
+ add(&hadoop,
+ "hadoop",
+ "Path to `hadoop' script (used for copying packages)",
+ "hadoop");
+
+ add(&hdfs,
+ "hdfs",
+ "The ip:port of the NameNode service",
+ "localhost:9000");
+
+ add(&package,
+ "package",
+ "Package to upload into HDFS and copy into command's\n"
+ "working directory (requires `hadoop', see --hadoop)");
+
+ add(&overwrite,
+ "overwrite",
+ "Overwrite the package in HDFS if it already exists",
+ false);
+ }
+
+ Option<string> master;
+ Option<string> name;
+ Option<string> command;
+ string resources;
+ string hadoop;
+ string hdfs;
+ Option<string> package;
+ bool overwrite;
+};
+
+
+class CommandScheduler : public Scheduler
+{
+public:
+ CommandScheduler(
+ const string& _name,
+ const string& _command,
+ const string& _resources,
+ const Option<string>& _uri)
+ : name(_name),
+ command(_command),
+ resources(_resources),
+ uri(_uri),
+ launched(false) {}
+
+ virtual ~CommandScheduler() {}
+
+ virtual void registered(
+ SchedulerDriver*,
+ const FrameworkID&,
+ const MasterInfo&) {}
+
+ virtual void reregistered(
+ SchedulerDriver*,
+ const MasterInfo& masterInfo) {}
+
+ virtual void disconnected(
+ SchedulerDriver* driver) {}
+
+ virtual void resourceOffers(
+ SchedulerDriver* driver,
+ const vector<Offer>& offers)
+ {
+ static const Try<Resources> TASK_RESOURCES = Resources::parse(resources);
+
+ if (TASK_RESOURCES.isError()) {
+ cerr << "Failed to parse resources '" << resources
+ << "': " << TASK_RESOURCES.error() << endl;
+ driver->abort();
+ return;
+ }
+
+ if (launched) {
+ foreach (const Offer& offer, offers) {
+ driver->declineOffer(offer.id());
+ }
+ }
+
+ const Offer& offer = offers.front();
+
+ // TODO(benh): Make sure offer has enough resources.
+
+ TaskInfo task;
+ task.set_name(name);
+ task.mutable_task_id()->set_value(name);
+ task.mutable_slave_id()->MergeFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(TASK_RESOURCES.get());
+ task.mutable_command()->set_value(command);
+ if (uri.isSome()) task.mutable_command()->add_uris()->set_value(uri.get());
+
+ vector<TaskInfo> tasks;
+ tasks.push_back(task);
+
+ driver->launchTasks(offer.id(), tasks);
+
+ launched = true;
+ }
+
+ virtual void offerRescinded(
+ SchedulerDriver* driver,
+ const OfferID& offerId) {}
+
+ virtual void statusUpdate(
+ SchedulerDriver* driver,
+ const TaskStatus& status)
+ {
+ CHECK_EQ(name, status.task_id().value());
+ if (status.state() == TASK_FINISHED || status.state() == TASK_FAILED) {
+ driver->stop();
+ }
+ }
+
+ virtual void frameworkMessage(
+ SchedulerDriver* driver,
+ const ExecutorID& executorId,
+ const SlaveID& slaveId,
+ const string& data) {}
+
+ virtual void slaveLost(
+ SchedulerDriver* driver,
+ const SlaveID& sid) {}
+
+ virtual void executorLost(
+ SchedulerDriver* driver,
+ const ExecutorID& executorID,
+ const SlaveID& slaveID,
+ int status) {}
+
+ virtual void error(
+ SchedulerDriver* driver,
+ const string& message) {}
+
+private:
+ const string name;
+ const string command;
+ const string resources;
+ const Option<string> uri;
+
+ bool launched;
+};
+
+
+int main(int argc, char** argv)
+{
+ Flags flags;
+
+ bool help;
+ flags.add(&help,
+ "help",
+ "Prints this help message",
+ false);
+
+ // Load flags from environment and command line.
+ Try<Nothing> load = flags.load(None(), argc, argv);
+
+ if (load.isError()) {
+ cerr << load.error() << endl;
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ if (help) {
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ if (flags.master.isNone()) {
+ cerr << "Missing --master=IP:PORT" << endl;
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ UPID master("master@" + flags.master.get());
+
+ if (!master) {
+ cerr << "Could not parse --master=" << flags.master.get() << endl;
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ if (flags.name.isNone()) {
+ cerr << "Missing --name=NAME" << endl;
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ if (flags.command.isNone()) {
+ cerr << "Missing --command=COMMAND" << endl;
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ // Copy the package to HDFS if requested save it's location as a URI
+ // for passing to the command (in CommandInfo).
+ Option<string> uri = None();
+
+ if (flags.package.isSome()) {
+ HDFS hdfs(flags.hadoop);
+
+ // TODO(benh): If HDFS is not properly configured with
+ // 'fs.default.name' then we'll copy to the local
+ // filesystem. Currently this will silently fail on our end (i.e.,
+ // the 'copyFromLocal' will be sucessful) but we'll fail to
+ // download the URI when we launch the executor (unless it's
+ // already been uploaded before ...).
+
+ // Store the file at '/user/package'.
+ string path = path::join("/", os::user(), flags.package.get());
+
+ // Check if the file exists and remove it if we're overwriting.
+ Try<bool> exists = hdfs.exists(path);
+ if (exists.isError()) {
+ cerr << "Failed to check if file exists: " << exists.error() << endl;
+ return -1;
+ } else if (exists.get() && flags.overwrite) {
+ Try<Nothing> rm = hdfs.rm(path);
+ if (rm.isError()) {
+ cerr << "Failed to remove existing file: " << rm.error() << endl;
+ return -1;
+ }
+ } else if (exists.get()) {
+ cerr << "File already exists (see --overwrite)" << endl;
+ return -1;
+ }
+
+ Try<Nothing> copy = hdfs.copyFromLocal(flags.package.get(), path);
+ if (copy.isError()) {
+ cerr << "Failed to copy package: " << copy.error() << endl;
+ return -1;
+ }
+
+ // Now save the URI.
+ uri = "hdfs://" + flags.hdfs + path;
+ }
+
+ CommandScheduler scheduler(
+ flags.name.get(),
+ flags.command.get(),
+ flags.resources,
+ uri);
+
+ FrameworkInfo framework;
+ framework.set_user(os::user());
+ framework.set_name("");
+
+ MesosSchedulerDriver driver(&scheduler, framework, flags.master.get());
+
+ return driver.run() == DRIVER_STOPPED ? 0 : 1;
+}
http://git-wip-us.apache.org/repos/asf/mesos/blob/5141dd67/src/cli/mesos-ps
----------------------------------------------------------------------
diff --git a/src/cli/mesos-ps b/src/cli/mesos-ps
new file mode 100755
index 0000000..a6a8e9b
--- /dev/null
+++ b/src/cli/mesos-ps
@@ -0,0 +1,229 @@
+#!/usr/bin/env python2.6
+
+import datetime
+import json
+import resource
+import subprocess
+import sys
+import time
+import urllib
+
+from optparse import OptionParser
+
+USER_COLUMN_WIDTH = 4
+FRAMEWORK_COLUMN_WIDTH = 4
+TASK_COLUMN_WIDTH = 6
+SLAVE_COLUMN_WIDTH = 14
+MEM_COLUMN_WIDTH = 8
+TIME_COLUMN_WIDTH = 14
+
+
+# Helper that uses 'mesos-resolve' to resolve the master's IP:port.
+def resolve(master):
+ process = subprocess.Popen(
+ ['mesos-resolve', master],
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=False)
+
+ status = process.wait()
+ if status != 0:
+ print 'Failed to execute \'mesos-resolve %s\':\n' % master
+ print process.stderr.read()
+ sys.exit(1)
+
+ result = process.stdout.read()
+ process.stdout.close()
+ process.stderr.close()
+ return result
+
+
+# Helper to determine the number of open file descriptors specified by
+# ulimit (since resource.RLIMIT_NOFILE is not always accurate).
+def ulimit(args):
+ command = args if isinstance(args, list) else [ args ]
+ command.insert(0, 'ulimit')
+ process = subprocess.Popen(
+ command,
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=False)
+
+ status = process.wait()
+ if status != 0:
+ print 'Failed to execute \'ulimit %s\':\n' % ' '.join(command)
+ print process.stderr.read()
+ sys.exit(1)
+
+ result = process.stdout.read()
+ process.stdout.close()
+ process.stderr.close()
+ return result
+
+class Slave:
+ def __init__(self, slave):
+ self.slave = slave
+ self.process = None
+ self.statistics = None
+
+ def hostname(self):
+ return self.slave['hostname']
+
+ def curl(self):
+ if self.process is None:
+ pid = self.slave['pid']
+ url = 'http://' + pid[len('slave(1)@'):] + '/monitor/statistics.json'
+ self.process = subprocess.Popen(
+ ['curl', '-s', url],
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=False)
+
+ def load(self):
+ if self.process is None:
+ self.curl()
+ if self.statistics is None:
+ status = self.process.wait()
+ if status != 0:
+ print 'Failed to execute \'curl\':\n'
+ print self.process.stderr.read()
+ sys.exit(1)
+ self.statistics = json.loads(self.process.stdout.read())
+ self.process.stdout.close()
+ self.process.stderr.close()
+
+ def cpus_time_secs(self, task):
+ self.load()
+ framework_id = task['framework_id']
+ executor_id = task['executor_id']
+ if executor_id == "": executor_id = task['id']
+ for i in range(len(self.statistics)):
+ entry = self.statistics[i]
+ if entry['framework_id'] == framework_id and \
+ entry['executor_id'] == executor_id:
+ return entry['statistics']['cpus_system_time_secs'] + \
+ entry['statistics']['cpus_user_time_secs']
+ return None
+
+ def mem_rss_bytes(self, task):
+ self.load()
+ framework_id = task['framework_id']
+ executor_id = task['executor_id']
+ if executor_id == "": executor_id = task['id']
+ for i in range(len(self.statistics)):
+ entry = self.statistics[i]
+ if entry['framework_id'] == framework_id and \
+ entry['executor_id'] == executor_id:
+ return entry['statistics']['mem_rss_bytes']
+ return None
+
+
+# Define the columns.
+class Column:
+ def __init__(self, title, padding):
+ self.title = title
+ self.padding = padding
+
+ def width(self):
+ return len(self.title) + self.padding
+
+ def format(self, text):
+ if text is None:
+ return ' ' * self.width()
+
+ text = str(text)
+
+ # If 'text' is less than the width then add spaces.
+ # Otherwise, abbreviate and add a space.
+ if len(text) < self.width():
+ spaces = ' ' * (self.width() - len(text))
+ text += spaces
+ else:
+ text = text[:self.width() - 4]
+ text += '... '
+ return text
+
+
+def main():
+ # Parse options for this script.
+ parser = OptionParser()
+ parser.add_option('--master')
+ (options, args) = parser.parse_args(sys.argv)
+
+ if options.master is None:
+ print "Missing --master\n"
+ parser.print_help()
+ exit(-1)
+
+ url = 'http://' + resolve(options.master) + '/master/state.json'
+ file = urllib.urlopen(url)
+ state = json.loads(file.read())
+ file.close()
+
+ # Build a dict from slave ID to `slaves'.
+ slaves = {}
+ for slave in state['slaves']:
+ slaves[slave['id']] = Slave(slave)
+
+ # Initiate the curl requests in batches less than the open file
+ # descriptor limit.
+ fd_limit = ulimit('-Sn')
+
+ batch = []
+ for slave in slaves.values():
+ if len(batch) == fd_limit:
+ for slave in batch:
+ slave.load() # Forces close of open file descriptors.
+ batch = []
+ slave.curl()
+ batch.append(slave)
+
+ columns = {}
+
+ columns[0] = Column('USER', USER_COLUMN_WIDTH)
+ columns[1] = Column('FRAMEWORK', FRAMEWORK_COLUMN_WIDTH)
+ columns[2] = Column('TASK', TASK_COLUMN_WIDTH)
+ columns[3] = Column('SLAVE', SLAVE_COLUMN_WIDTH)
+ columns[4] = Column('MEM', MEM_COLUMN_WIDTH)
+ columns[5] = Column('TIME', TIME_COLUMN_WIDTH)
+
+ for i in columns:
+ sys.stdout.write(columns[i].title)
+ sys.stdout.write(' ' * columns[i].padding)
+
+ for framework in state['frameworks']:
+ for task in framework['tasks']:
+ sys.stdout.write('\n')
+ sys.stdout.write(columns[0].format(framework['user']))
+ sys.stdout.write(columns[1].format(framework['name']))
+ sys.stdout.write(columns[2].format(task['name']))
+ sys.stdout.write(columns[3].format(slaves[task['slave_id']].hostname()))
+
+ # Get memory usage and cpus *time* from the slave (we can't get
+ # cpus % unless we have a previous value).
+ mem_rss_bytes = slaves[task['slave_id']].mem_rss_bytes(task)
+ cpus_time_secs = slaves[task['slave_id']].cpus_time_secs(task)
+
+ if mem_rss_bytes is not None:
+ MB = 1024.0 * 1024.0
+ mem_rss_megabytes = mem_rss_bytes / MB
+ s = "{0:.1f} MB".format(mem_rss_megabytes)
+ sys.stdout.write(columns[4].format(s))
+ else:
+ sys.stdout.write(columns[4].format(None))
+
+ if cpus_time_secs is not None:
+ dt = datetime.datetime.utcfromtimestamp(cpus_time_secs)
+ sys.stdout.write(columns[5].format(dt.strftime('%H:%M:%S.%f')))
+ else:
+ sys.stdout.write(columns[5].format(None))
+
+ sys.stdout.write('\n')
+ sys.stdout.flush()
+
+
+if __name__ == "__main__":
+ main()
http://git-wip-us.apache.org/repos/asf/mesos/blob/5141dd67/src/cli/mesos.cpp
----------------------------------------------------------------------
diff --git a/src/cli/mesos.cpp b/src/cli/mesos.cpp
index f633ae1..17a9f0c 100644
--- a/src/cli/mesos.cpp
+++ b/src/cli/mesos.cpp
@@ -1,8 +1,102 @@
-#include <stout/exit.hpp>
+#include <unistd.h>
+
+#include <iostream>
+#include <list>
+
+#include <stout/os.hpp>
+#include <stout/path.hpp>
+#include <stout/strings.hpp>
+#include <stout/try.hpp>
+
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::list;
+using std::string;
+
+
+void usage(const char* argv0)
+{
+ // Get a list of available commands.
+ const string& PATH = os::getenv("PATH");
+
+ list<string> commands;
+
+ foreach (const string& path, strings::split(PATH, ":")) {
+ Try<list<string> > matches = os::glob(path::join(path, "mesos-*"));
+ if (matches.isSome()) {
+ foreach (const string& match, matches.get()) {
+ Try<bool> access = os::access(match, X_OK);
+ if (access.isSome() && access.get()) {
+ Try<string> basename = os::basename(match);
+ if (basename.isSome()) {
+ commands.push_back(basename.get().substr(6));
+ }
+ }
+ }
+ }
+ }
+
+ cerr
+ << "Usage: " << os::basename(argv0).get() << " <command> [OPTIONS]"
+ << endl
+ << endl
+ << "Available commands:" << endl
+ << " help" << endl;
+
+ foreach (const string& command, commands) {
+ cerr << " " << command << endl;
+ }
+}
int main(int argc, char** argv)
{
- EXIT(1) << "Unimplemented";
+ // Try and add the absolute dirname of argv[0] to PATH so we can
+ // find commands (since our installation directory might not be on
+ // the path).
+ Try<string> dirname = os::dirname(argv[0]);
+ if (dirname.isSome()) {
+ Try<string> realpath = os::realpath(dirname.get());
+ if (realpath.isSome()) {
+ os::setenv("PATH", realpath.get() + ":" + os::getenv("PATH"));
+ }
+ }
+
+ if (argc < 2) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ // Now dispatch to any mesos-'command' on PATH.
+ if (string(argv[1]) == "help") {
+ if (argc == 2) {
+ usage(argv[0]);
+ return 0;
+ } else {
+ // 'mesos help command' => 'mesos command --help'
+ argv[1] = argv[2];
+ argv[2] = (char*) "--help";
+ return main(argc, argv);
+ }
+ } else if (string(argv[1]).find("--") == 0) {
+ cerr << "Not expecting '" << argv[1] << "' before command" << endl;
+ usage(argv[0]);
+ return -1;
+ } else {
+ string command = argv[1];
+ string executable = "mesos-" + command;
+ argv[1] = (char*) executable.c_str();
+ execvp(executable.c_str(), argv + 1);
+ if (errno == ENOENT) {
+ cerr << "'" << command << "' is not a valid command "
+ << "(or can not be found)" << endl;
+ } else {
+ cerr << "Failed to execute " << command
+ << ": " << strerror(errno) << endl;
+ }
+ return -1;
+ }
+
return 0;
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/5141dd67/src/cli/resolve.cpp
----------------------------------------------------------------------
diff --git a/src/cli/resolve.cpp b/src/cli/resolve.cpp
new file mode 100644
index 0000000..b05f510
--- /dev/null
+++ b/src/cli/resolve.cpp
@@ -0,0 +1,135 @@
+/**
+ * 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 <iostream>
+
+#include <process/future.hpp>
+#include <process/pid.hpp>
+
+#include <stout/duration.hpp>
+#include <stout/flags.hpp>
+#include <stout/none.hpp>
+#include <stout/nothing.hpp>
+#include <stout/os.hpp>
+#include <stout/strings.hpp>
+#include <stout/try.hpp>
+
+#include "detector/detector.hpp"
+
+using namespace mesos;
+using namespace mesos::internal;
+
+using process::Future;
+using process::UPID;
+
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::string;
+
+
+void usage(const char* argv0, const flags::FlagsBase& flags)
+{
+ cerr << "Usage: " << os::basename(argv0).get() << " <master>" << endl
+ << endl
+ << "Supported options:" << endl
+ << flags.usage();
+}
+
+
+int main(int argc, char** argv)
+{
+ flags::FlagsBase flags;
+
+ bool help;
+ flags.add(&help,
+ "help",
+ "Prints this help message",
+ false);
+
+ Duration timeout;
+ flags.add(&timeout,
+ "timeout",
+ "How long to wait to resolve master",
+ Seconds(5));
+
+ bool verbose;
+ flags.add(&verbose,
+ "verbose",
+ "Be verbose",
+ false);
+
+ // Load flags from environment and command line.
+ Try<Nothing> load = flags.load(None(), argc, argv);
+
+ if (load.isError()) {
+ cerr << load.error() << endl;
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ if (help) {
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ if (argc < 2) {
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ Option<string> master = None();
+
+ // Find 'master' argument (the only argument not prefixed by '--').
+ for (int i = 1; i < argc; i++) {
+ const std::string arg(strings::trim(argv[i]));
+ if (arg.find("--") != 0) {
+ if (master.isSome()) {
+ // There should only be one non-flag argument.
+ cerr << "Ambiguous 'master': "
+ << master.get() << " and " << arg << endl;
+ usage(argv[0], flags);
+ return -1;
+ } else {
+ master = arg;
+ }
+ }
+ }
+
+ if (master.isNone()) {
+ cerr << "Missing 'master'" << endl;
+ usage(argv[0], flags);
+ return -1;
+ }
+
+ Future<UPID> pid = mesos::internal::detect(master.get(), !verbose);
+
+ if (!pid.await(timeout)) {
+ cerr << "Failed to detect master from '" << master.get()
+ << "' within " << timeout << endl;
+ return -1;
+ } else if (pid.isFailed()) {
+ cerr << "Failed to detect master from '" << master.get()
+ << "': " << pid.failure() << endl;
+ return -1;
+ }
+
+ cout << string(pid.get()).substr(7) << endl;
+
+ return 0;
+}