blob: b51112e1ae261892715a6a94ae672649d75a5dcb [file] [log] [blame]
// 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 __MESOS_CONTAINERIZER_IO_SWITCHBOARD_HPP__
#define __MESOS_CONTAINERIZER_IO_SWITCHBOARD_HPP__
#include <string>
#include <process/future.hpp>
#include <process/http.hpp>
#include <process/owned.hpp>
#include <process/socket.hpp>
#include <stout/try.hpp>
#include <mesos/slave/containerizer.hpp>
#include <mesos/slave/container_logger.hpp>
#include "slave/flags.hpp"
#include "slave/containerizer/mesos/isolator.hpp"
namespace mesos {
namespace internal {
namespace slave {
// The `IOSwitchboard` is a component in the agent whose job it is to
// instantiate an `IOSwitchboardServer` that can be used to feed the
// stdin to a container from an external source, as well as redirect
// the stdin/stdout of a container to multiple targets.
//
// The primary motivation of this component is to enable support in
// mesos similar to `docker attach` and `docker exec` whereby an
// external client can attach to the stdin/stdout/stderr of a running
// container as well as launch arbitrary subcommands inside a
// container and attach to its stdin/stdout/stderr.
//
// The I/O switchboard is integrated with `MesosContainerizer` through
// the `Isolator` interface.
class IOSwitchboard : public MesosIsolatorProcess
{
public:
static Try<IOSwitchboard*> create(
const Flags& flags,
bool local);
~IOSwitchboard() override;
bool supportsNesting() override;
bool supportsStandalone() override;
process::Future<Nothing> recover(
const std::vector<mesos::slave::ContainerState>& states,
const hashset<ContainerID>& orphans) override;
process::Future<Option<mesos::slave::ContainerLaunchInfo>> prepare(
const ContainerID& containerId,
const mesos::slave::ContainerConfig& containerConfig) override;
process::Future<mesos::slave::ContainerLimitation> watch(
const ContainerID& containerId) override;
process::Future<Nothing> cleanup(
const ContainerID& containerId) override;
// Connect to the `IOSwitchboard` associated with `containerId`.
process::Future<process::http::Connection> connect(
const ContainerID& containerId) const;
// Transfer ownership of a `ContainerIO` struct for a given
// container out of the `IOSwitchboard` and into the caller.
process::Future<Option<mesos::slave::ContainerIO>> extractContainerIO(
const ContainerID& containerID);
// Helper function that returns `true` if `IOSwitchboardServer`
// needs to be enabled for the given `ContainerConfig`. It must
// be enabled for `DEBUG` containers and ones that need `TTYInfo`.
static bool requiresServer(
const mesos::slave::ContainerConfig& containerConfig);
private:
struct Info
{
Info(Option<pid_t> _pid, const process::Future<Option<int>>& _status)
: pid(_pid),
status(_status) {}
Option<pid_t> pid;
process::Future<Option<int>> status;
process::Promise<mesos::slave::ContainerLimitation> limitation;
};
IOSwitchboard(
const Flags& flags,
bool local,
process::Owned<mesos::slave::ContainerLogger> logger);
process::Future<Option<mesos::slave::ContainerLaunchInfo>> _prepare(
const ContainerID& containerId,
const mesos::slave::ContainerConfig& containerConfig,
const mesos::slave::ContainerIO& loggerIO);
process::Future<process::http::Connection> _connect(
const ContainerID& containerId) const;
process::Future<Option<mesos::slave::ContainerIO>> _extractContainerIO(
const ContainerID& containerID);
#ifndef __WINDOWS__
void reaped(
const ContainerID& containerId,
const process::Future<Option<int>>& future);
#endif // __WINDOWS__
Flags flags;
bool local;
process::Owned<mesos::slave::ContainerLogger> logger;
hashmap<ContainerID, process::Owned<Info>> infos;
// We use a separate hashmap to hold the `ContainerIO` for each
// container because we need to maintain this information even in
// the case were we only instantiate the logger and never spawn an
// `IOSwitchbaordProcess`. Also, the lifetime of the `ContainerIO`
// is shorter lived than the `Info` struct, as it should be removed
// from this hash as soon as ownership is transferred out of the
// `IOSwitchboard` via a call to `extractContainerIO()`.
hashmap<ContainerID, mesos::slave::ContainerIO> containerIOs;
};
#ifndef __WINDOWS__
// The `IOSwitchboardServer` encapsulates the server side logic for
// redirecting the `stdin/stdout/stderr` of a container to/from
// multiple sources/targets. It runs an HTTP server over a unix domain
// socket in order to process incoming `ATTACH_CONTAINER_INPUT` and
// `ATTACH_CONTAINER_OUTPUT` calls and redirect a containers
// `stdin/stdout/stderr` through them. In 'local' mode, it is run
// inside the agent itself. In 'non-local' mode, it is run as an
// external process to survive agent restarts.
class IOSwitchboardServerProcess;
class IOSwitchboardServer
{
public:
// The set of flags to pass to the io switchboard server when
// launched in an external binary.
struct Flags : public virtual flags::FlagsBase
{
Flags()
{
setUsageMessage(
"Usage: " + stringify(NAME) + " [options]\n"
"The io switchboard server is designed to feed stdin to a container\n"
"from an external source, as well as redirect the stdin/stdout of a\n"
"container to multiple targets.\n"
"\n"
"It runs an HTTP server over a unix domain socket in order to process\n"
"incoming `ATTACH_CONTAINER_INPUT` and `ATTACH_CONTAINER_OUTPUT`\n"
"calls and redirect a containers `stdin/stdout/stderr` through them.\n"
"\n"
"The primary motivation of this component is to enable support in\n"
"mesos similar to `docker attach` and `docker exec` whereby an\n"
"external client can attach to the stdin/stdout/stderr of a running\n"
"container as well as launch arbitrary subcommands inside a container\n"
"and attach to its stdin/stdout/stderr.\n");
add(&Flags::tty,
"tty",
"If a pseudo terminal has been allocated for the container.",
false);
add(&Flags::stdin_to_fd,
"stdin_to_fd",
"The file descriptor where incoming stdin data should be written.");
add(&Flags::stdout_from_fd,
"stdout_from_fd",
"The file descriptor that should be read to consume stdout data.");
add(&Flags::stdout_to_fd,
"stdout_to_fd",
"A file descriptor where data read from\n"
"'stdout_from_fd' should be redirected to.");
add(&Flags::stderr_from_fd,
"stderr_from_fd",
"The file descriptor that should be read to consume stderr data.");
add(&Flags::stderr_to_fd,
"stderr_to_fd",
"A file descriptor where data read from\n"
"'stderr_from_fd' should be redirected to.");
add(&Flags::wait_for_connection,
"wait_for_connection",
"A boolean indicating whether the server should wait for the\n"
"first connection before reading any data from the '*_from_fd's.",
false);
add(&Flags::socket_path,
"socket_address",
"The path of the unix domain socket this\n"
"io switchboard should attach itself to.");
add(&Flags::heartbeat_interval,
"heartbeat_interval",
"A heartbeat interval (e.g. '5secs', '10mins') for messages to\n"
"be sent to any open 'ATTACH_CONTAINER_OUTPUT' connections.");
}
bool tty;
Option<int> stdin_to_fd;
Option<int> stdout_from_fd;
Option<int> stdout_to_fd;
Option<int> stderr_from_fd;
Option<int> stderr_to_fd;
Option<std::string> socket_path;
bool wait_for_connection;
Option<Duration> heartbeat_interval;
};
static const char NAME[];
static Try<process::Owned<IOSwitchboardServer>> create(
bool tty,
int stdinToFd,
int stdoutFromFd,
int stdoutToFd,
int stderrFromFd,
int stderrToFd,
const std::string& socketPath,
bool waitForConnection = false,
Option<Duration> heartbeatInterval = None());
~IOSwitchboardServer();
// Run the io switchboard server.
process::Future<Nothing> run();
// Forcibly unblock the io switchboard server if it
// has been started with `waitForConnection` set to `true`.
process::Future<Nothing> unblock();
private:
IOSwitchboardServer(
bool tty,
int stdinToFd,
int stdoutFromFd,
int stdoutToFd,
int stderrFromFd,
int stderrToFd,
const process::network::unix::Socket& socket,
bool waitForConnection,
Option<Duration> heartbeatInterval);
process::Owned<IOSwitchboardServerProcess> process;
};
#endif // __WINDOWS__
} // namespace slave {
} // namespace internal {
} // namespace mesos {
#endif // __MESOS_CONTAINERIZER_IO_SWITCHBOARD_HPP__