Added a 'Docker' abstraction.
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/ab6db9b5 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/ab6db9b5 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/ab6db9b5 Branch: refs/heads/master Commit: ab6db9b5adab7d08e24e7004b8774f71c9014c2c Parents: dcc87a1 Author: Benjamin Hindman <[email protected]> Authored: Sun Jun 22 17:56:05 2014 -0700 Committer: Benjamin Hindman <[email protected]> Committed: Mon Aug 4 15:08:15 2014 -0700 ---------------------------------------------------------------------- src/Makefile.am | 2 + src/docker/docker.cpp | 170 +++++++++++++++++++++++++++++++++++++++++++++ src/docker/docker.hpp | 72 +++++++++++++++++++ 3 files changed, 244 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/ab6db9b5/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index 411cff4..ef99788 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -234,6 +234,8 @@ libmesos_no_3rdparty_la_SOURCES = \ common/thread.cpp \ common/type_utils.cpp \ common/values.cpp \ + docker/docker.hpp \ + docker/docker.cpp \ exec/exec.cpp \ files/files.cpp \ local/local.cpp \ http://git-wip-us.apache.org/repos/asf/mesos/blob/ab6db9b5/src/docker/docker.cpp ---------------------------------------------------------------------- diff --git a/src/docker/docker.cpp b/src/docker/docker.cpp new file mode 100644 index 0000000..db31ba3 --- /dev/null +++ b/src/docker/docker.cpp @@ -0,0 +1,170 @@ +#include <map> +#include <vector> + +#include <stout/lambda.hpp> +#include <stout/strings.hpp> + +#include <process/check.hpp> +#include <process/collect.hpp> +#include <process/io.hpp> + +#include "docker/docker.hpp" + +using namespace process; + +using std::list; +using std::map; +using std::string; +using std::vector; + + +string Docker::Container::name() const +{ + map<string, JSON::Value>::const_iterator entry = + json.values.find("Name"); + CHECK(entry != json.values.end()); + JSON::Value value = entry->second; + CHECK(value.is<JSON::String>()); + return value.as<JSON::String>().value; +} + + +Future<Option<int> > Docker::run(const string& image) const +{ + Try<Subprocess> s = subprocess( + path + " run " + image, + Subprocess::PIPE(), + Subprocess::PIPE(), + Subprocess::PIPE()); + + if (s.isError()) { + return Failure(s.error()); + } + + return s.get().status(); +} + + +Future<Option<int> > Docker::kill(const string& container) const +{ + Try<Subprocess> s = subprocess( + path + " kill " + container, + Subprocess::PIPE(), + Subprocess::PIPE(), + Subprocess::PIPE()); + + if (s.isError()) { + return Failure(s.error()); + } + + return s.get().status(); +} + + +Future<Docker::Container> Docker::inspect(const string& container) const +{ + Try<Subprocess> s = subprocess( + path + " inspect " + container, + Subprocess::PIPE(), + Subprocess::PIPE(), + Subprocess::PIPE()); + + if (s.isError()) { + // TODO(benh): Include stdout and stderr in error message. + return Failure(s.error()); + } + + return s.get().status() + .then(lambda::bind(&Docker::_inspect, s.get())); +} + + +Future<Docker::Container> Docker::_inspect(const Subprocess& s) +{ + // Check the exit status of 'docker ps'. + CHECK_READY(s.status()); + + Option<int> status = s.status().get(); + + if (status.isSome() && status.get() != 0) { + // TODO(benh): Include stdout and stderr in error message. + return Failure("Failed to do 'docker ps'"); + } + + // Read to EOF. + // TODO(benh): Read output asynchronously. + CHECK_SOME(s.out()); + string output = io::read(s.out().get()).get(); + + Try<JSON::Array> parse = JSON::parse<JSON::Array>(output); + + if (parse.isError()) { + return Failure("Failed to parse JSON: " + parse.error()); + } + + JSON::Array array = parse.get(); + + // Skip the container if it no longer exists. + if (array.values.size() == 1) { + CHECK(array.values.front().is<JSON::Object>()); + return Docker::Container(array.values.front().as<JSON::Object>()); + } + + // TODO(benh): Handle the case where the short container ID was + // not sufficiently unique and 'array.values.size() > 1'. + + return Failure("Failed to find container"); +} + + +Future<list<Docker::Container> > Docker::ps() const +{ + Try<Subprocess> s = subprocess( + path + " ps", + Subprocess::PIPE(), + Subprocess::PIPE(), + Subprocess::PIPE()); + + if (s.isError()) { + return Failure(s.error()); + } + + return s.get().status() + .then(lambda::bind(&Docker::_ps, Docker(path), s.get())); +} + + +Future<list<Docker::Container> > Docker::_ps( + const Docker& docker, + const Subprocess& s) +{ + // Check the exit status of 'docker ps'. + CHECK_READY(s.status()); + + Option<int> status = s.status().get(); + + if (status.isSome() && status.get() != 0) { + // TODO(benh): Include stdout and stderr in error message. + return Failure("Failed to do 'docker ps'"); + } + + // Read to EOF. + // TODO(benh): Read output asynchronously. + CHECK_SOME(s.out()); + string output = io::read(s.out().get()).get(); + + vector<string> lines = strings::split(output, "\n"); + + // Skip the header. + CHECK_NE(0, lines.size()); + lines.erase(lines.begin()); + + list<Future<Docker::Container> > futures; + + foreach (const string& line, lines) { + // Inspect the container. + futures.push_back(docker.inspect(strings::split(line, "\n")[0])); + } + + return collect(futures); +} http://git-wip-us.apache.org/repos/asf/mesos/blob/ab6db9b5/src/docker/docker.hpp ---------------------------------------------------------------------- diff --git a/src/docker/docker.hpp b/src/docker/docker.hpp new file mode 100644 index 0000000..26d6ec3 --- /dev/null +++ b/src/docker/docker.hpp @@ -0,0 +1,72 @@ +/** + * 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. + */ + +#ifndef __DOCKER_HPP__ +#define __DOCKER_HPP__ + +#include <list> +#include <string> + +#include <process/future.hpp> +#include <process/subprocess.hpp> + +#include <stout/json.hpp> +#include <stout/option.hpp> + +// Abstraction for working with Docker (modeled on CLI). +class Docker +{ +public: + class Container + { + public: + Container(const JSON::Object& json) : json(json) {} + + // Returns the name of the container. + std::string name() const; + + private: + JSON::Object json; // JSON returned from 'docker inspect'. + }; + + // Uses the specified path to the Docker CLI tool. + Docker(const std::string& path) : path(path) {} + + // Performs 'docker run IMAGE'. + process::Future<Option<int> > run(const std::string& image) const; + + // Performs 'docker kill CONTAINER'. + process::Future<Option<int> > kill(const std::string& container) const; + + // Performs 'docker inspect CONTAINER'. + process::Future<Container> inspect(const std::string& container) const; + + // Performs 'docker ps'. + process::Future<std::list<Container> > ps() const; + +private: + // Continuations. + static process::Future<Container> _inspect(const process::Subprocess& s); + static process::Future<std::list<Container> > _ps( + const Docker& docker, + const process::Subprocess& s); + + const std::string path; +}; + +#endif // __DOCKER_HPP__
