Apache Mesos
docker_common.hpp
Go to the documentation of this file.
1 // Licensed to the Apache Software Foundation (ASF) under one
2 // or more contributor license agreements. See the NOTICE file
3 // distributed with this work for additional information
4 // regarding copyright ownership. The ASF licenses this file
5 // to you under the Apache License, Version 2.0 (the
6 // "License"); you may not use this file except in compliance
7 // with the License. You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #ifndef __TEST_DOCKER_COMMON_HPP__
18 #define __TEST_DOCKER_COMMON_HPP__
19 
20 #include <string>
21 
22 #include <process/future.hpp>
23 #include <process/gtest.hpp>
24 #include <process/io.hpp>
25 #include <process/owned.hpp>
26 #include <process/subprocess.hpp>
27 
28 #include <stout/format.hpp>
29 #include <stout/gtest.hpp>
30 #include <stout/lambda.hpp>
31 #include <stout/nothing.hpp>
32 #include <stout/option.hpp>
33 #include <stout/try.hpp>
34 
35 #include <stout/os/mkdtemp.hpp>
36 
37 #include "docker/docker.hpp"
38 
39 #include "tests/flags.hpp"
40 
41 namespace mesos {
42 namespace internal {
43 namespace tests {
44 
45 #ifdef __WINDOWS__
46 // The following image is the microsoft/nanoserver image with
47 // ContainerAdministrator as the default user. There are some permission bugs
48 // with accessing volume mounts in process (but not Hyper-V) isolation as
49 // the regular ContainerUser user, but accesing them as ContainerAdministrator
50 // works fine.
51 static constexpr char DOCKER_TEST_IMAGE[] = "akagup/nano-admin";
52 
53 // We use a custom HTTP(S) server here, because the official `microsoft/iis`
54 // HTTP server image is ~20x larger than this one, so pulling it takes too
55 // long. This server supports HTTP and HTTPS listening on port 80 and 443.
56 // For more information, see https://github.com/gupta-ak/https-server.
57 static constexpr char DOCKER_HTTP_IMAGE[] = "akagup/https-server";
58 static constexpr char DOCKER_HTTP_COMMAND[] = "http.exe";
59 static constexpr char DOCKER_HTTPS_IMAGE[] = "akagup/https-server";
60 static constexpr char DOCKER_HTTPS_COMMAND[] = "http.exe";
61 #else
62 static constexpr char DOCKER_TEST_IMAGE[] = "alpine";
63 
64 // The HTTP server is netcat running on alpine.
65 static constexpr char DOCKER_HTTP_IMAGE[] = "alpine";
66 static constexpr char DOCKER_HTTP_COMMAND[] =
67  "nc -lk -p 80 -e echo -e \"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\"";
68 
69 // Refer to https://github.com/qianzhangxa/https-server for
70 // how the Docker image `zhq527725/https-server` works.
71 static constexpr char DOCKER_HTTPS_IMAGE[] = "zhq527725/https-server";
72 static constexpr char DOCKER_HTTPS_COMMAND[] = "python https_server.py 443";
73 #endif // __WINDOWS__
74 
75 constexpr char DOCKER_IPv6_NETWORK[] = "mesos-docker-ip6-test";
76 
77 
78 inline process::Future<Nothing> pullDockerImage(const std::string& imageName)
79 {
81  Docker::create(tests::flags.docker, tests::flags.docker_socket, false);
82 
83  if (docker.isError()) {
84  return process::Failure(docker.error());
85  }
86 
87  const Try<std::string> directory = os::mkdtemp();
88  if (directory.isError()) {
89  return process::Failure(docker.error());
90  }
91 
92  return docker.get()->pull(directory.get(), imageName)
93  .then([]() {
94  // `Docker::pull` returns a `Future<Docker::Image`>, but we only really
95  // if the pull was successful, so we just return `Nothing` to match the
96  // return type of `pullDockerImage`.
97  return Nothing();
98  })
99  .onAny([directory]() -> process::Future<Nothing> {
100  Try<Nothing> rmdir = os::rmdir(directory.get());
101  if (rmdir.isError()) {
102  return process::Failure(rmdir.error());
103  }
104  return Nothing();
105  });
106 }
107 
108 
110 {
111  // Docker IPv6 is not supported on Windows, so no-op on that platform.
112  // TODO(akagup): Remove the #ifdef when Windows supports IPv6 networks
113  // in docker containers. See MESOS-8566.
114 #ifndef __WINDOWS__
115  // Create a Docker user network with IPv6 enabled.
116  Try<std::string> dockerCommand = strings::format(
117  "docker network create --driver=bridge --ipv6 "
118  "--subnet=fd01::/64 %s",
119  DOCKER_IPv6_NETWORK);
120 
122  dockerCommand.get(),
123  process::Subprocess::PATH("/dev/null"),
124  process::Subprocess::PATH("/dev/null"),
126 
127  ASSERT_SOME(s) << "Unable to create the Docker IPv6 network: "
129 
131 
132  // Wait for the network to be created.
133  AWAIT_READY(s->status());
134  AWAIT_READY(err);
135 
136  ASSERT_SOME(s->status().get());
137  ASSERT_EQ(s->status()->get(), 0)
138  << "Unable to create the Docker IPv6 network "
139  << DOCKER_IPv6_NETWORK
140  << " : " << err.get();
141 #endif // __WINDOWS__
142 }
143 
144 
146 {
147  // Docker IPv6 is not supported on Windows, so no-op on that platform.
148  // TODO(akagup): Remove the #ifdef when Windows supports IPv6 networks
149  // in docker containers. See MESOS-8566.
150 #ifndef __WINDOWS__
151  // Delete the Docker user network.
152  Try<std::string> dockerCommand =
153  strings::format("docker network rm %s", DOCKER_IPv6_NETWORK);
154 
156  dockerCommand.get(),
157  process::Subprocess::PATH("/dev/null"),
158  process::Subprocess::PATH("/dev/null"),
160 
161  // This is best effort cleanup. In case of an error just a log an
162  // error.
163  ASSERT_SOME(s) << "Unable to delete the Docker IPv6 network: "
165 
167 
168  // Wait for the network to be deleted.
169  AWAIT_READY(s->status());
170  AWAIT_READY(err);
171 
172  ASSERT_SOME(s->status().get());
173  ASSERT_EQ(s->status()->get(), 0)
174  << "Unable to delete the Docker IPv6 network "
175  << DOCKER_IPv6_NETWORK
176  << " : " << err.get();
177 #endif // __WINDOWS__
178 }
179 
180 
182 {
183 #ifdef __WINDOWS__
184  // On Windows, there is no standard exit code for determining if a process
185  // as been killed. However, in Docker, it will not return 0.
187 #else
189 #endif // __WINDOWS__
190 }
191 
192 } // namespace tests {
193 } // namespace internal {
194 } // namespace mesos {
195 
196 #endif // __TEST_DOCKER_COMMON_HPP__
Try< Nothing > rmdir(const std::string &directory, bool recursive=true, bool removeRoot=true, bool continueOnError=false)
Definition: rmdir.hpp:42
Definition: nothing.hpp:16
T & get()&
Definition: try.hpp:80
const T & get() const
Definition: future.hpp:1294
void removeDockerIPv6UserNetwork()
Definition: docker_common.hpp:145
const mode_t SIGKILL
Definition: windows.hpp:335
Definition: check.hpp:33
Definition: future.hpp:668
Result< ProcessStatus > status(pid_t pid)
Definition: proc.hpp:166
static IO PATH(const std::string &path)
#define AWAIT_EXPECT_WEXITSTATUS_EQ(expected, actual)
Definition: gtest.hpp:661
Try< Subprocess > subprocess(const std::string &path, std::vector< std::string > argv, const Subprocess::IO &in=Subprocess::FD(STDIN_FILENO), const Subprocess::IO &out=Subprocess::FD(STDOUT_FILENO), const Subprocess::IO &err=Subprocess::FD(STDERR_FILENO), const flags::FlagsBase *flags=nullptr, const Option< std::map< std::string, std::string >> &environment=None(), const Option< lambda::function< pid_t(const lambda::function< int()> &)>> &clone=None(), const std::vector< Subprocess::ParentHook > &parent_hooks={}, const std::vector< Subprocess::ChildHook > &child_hooks={}, const std::vector< int_fd > &whitelist_fds={})
Forks a subprocess and execs the specified &#39;path&#39; with the specified &#39;argv&#39;, redirecting stdin...
Option< int_fd > err() const
Definition: subprocess.hpp:263
#define AWAIT_READY(actual)
Definition: gtest.hpp:282
void assertDockerKillStatus(process::Future< Option< int >> &status)
Definition: docker_common.hpp:181
Definition: agent.hpp:25
process::Future< Nothing > pullDockerImage(const std::string &imageName)
Definition: docker_common.hpp:78
static Try< process::Owned< Docker > > create(const std::string &path, const std::string &socket, bool validate=true, const Option< JSON::Object > &config=None())
const T & get() const &
Definition: option.hpp:119
static Try error(const E &e)
Definition: try.hpp:43
Definition: attributes.hpp:24
bool isError() const
Definition: try.hpp:78
void then(lambda::CallableOnce< X(const T &)> &&f, std::unique_ptr< Promise< X >> promise, const Future< T > &future)
Definition: future.hpp:1487
constexpr char DOCKER_IPv6_NETWORK[]
Definition: docker_common.hpp:75
void createDockerIPv6UserNetwork()
Definition: docker_common.hpp:109
Future< size_t > read(int_fd fd, void *data, size_t size)
Performs a single non-blocking read by polling on the specified file descriptor until any data can be...
Try< std::string > format(const std::string &s, const T &...t)
Definition: format.hpp:58
#define ASSERT_SOME(actual)
Definition: gtest.hpp:128
Definition: spec.hpp:35
#define AWAIT_EXPECT_WEXITSTATUS_NE(expected, actual)
Definition: gtest.hpp:708
Future< Option< int > > status() const
Exit status of this subprocess captured as a Future (completed when the subprocess exits)...
Definition: subprocess.hpp:289
Try< std::string > mkdtemp(const std::string &path=path::join(os::temp(),"XXXXXX"))
Definition: mkdtemp.hpp:38