blob: 56211f7243816ae2342c7c0d6d974c7fca3fb5a2 [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 __TEST_DOCKER_ARCHIVE_HPP__
#define __TEST_DOCKER_ARCHIVE_HPP__
#include <process/collect.hpp>
#include <process/future.hpp>
#include <process/owned.hpp>
#include <stout/error.hpp>
#include <stout/json.hpp>
#include <stout/jsonify.hpp>
#include <stout/nothing.hpp>
#include <stout/os.hpp>
#include <stout/path.hpp>
#include <stout/stringify.hpp>
#include <stout/try.hpp>
#include "common/command_utils.hpp"
#include "tests/containerizer/rootfs.hpp"
using process::Failure;
using process::Future;
namespace mesos {
namespace internal {
namespace tests {
// This represents a docker archive. It has the same format
// as that tarball generated by doing a 'docker save'.
class DockerArchive
{
public:
// Create a docker test image tarball in docker registry directory.
// Users can define own entrypoint/cmd as JSON array of JSON string
// (e.g., `[\"sh\", \"-c\"]`).
//
// NOTE: The default value for `environment` includes some environment
// variables which will cause problems if they are fed into one of Mesos'
// built-in executors. This is on purpose, as the environment variables
// of the image should not be passed into built-in executors. Tests that
// use a custom executor should consider overriding this default.
static Future<Nothing> create(
const std::string& directory,
const std::string& name,
const std::string& entrypoint = "null",
const std::string& cmd = "null",
const std::vector<std::string>& environment = {
{"LD_LIBRARY_PATH=invalid"},
{"LIBPROCESS_IP=invalid"},
{"LIBPROCESS_PORT=invalid"}})
{
Try<Nothing> mkdir = os::mkdir(directory, true);
if (mkdir.isError()) {
return Failure("Failed to create '" + directory + "': " + mkdir.error());
}
const std::string imagePath = path::join(directory, name);
mkdir = os::mkdir(imagePath);
if (mkdir.isError()) {
return Failure("Failed to create docker test image directory '" +
imagePath + "': " + mkdir.error());
}
const std::string layerId =
"815b809d588c80fd6ddf4d6ac244ad1c01ae4cbe0f91cc7480e306671ee9c346";
const std::string layerPath = path::join(imagePath, layerId);
// Create docker test image `repositories`.
JSON::Value repositories = JSON::parse(strings::format(
R"~(
{
"%s": {
"latest": "%s"
}
})~",
name,
layerId).get()).get();
Try<Nothing> write = os::write(
path::join(imagePath, "repositories"),
stringify(repositories));
if (write.isError()) {
return Failure("Failed to save docker test image 'repositories': " +
write.error());
}
mkdir = os::mkdir(layerPath);
if (mkdir.isError()) {
return Failure("Failed to create docker test image layer '" +
layerId + "': " + mkdir.error());
}
JSON::Value manifest = JSON::parse(strings::format(
R"~(
{
"id": "815b809d588c80fd6ddf4d6ac244ad1c01ae4cbe0f91cc7480e306671ee9c346",
"created": "2016-03-02T17:16:00.167415955Z",
"container": "eb53609036555d26c39bdccfa9850426934bdfde96111d099041689b2251a377",
"container_config": {
"Hostname": "eb5360903655",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ADD file:81ba6f20bdb99e6c13c434a577069860b6656908031162083b1ac9c02c71dd9f in /"
],
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"docker_version": "1.9.1",
"config": {
"Hostname": "eb5360903655",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": %s,
"Cmd": %s,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": %s,
"OnBuild": null,
"Labels": null
},
"architecture": "amd64",
"os": "linux"
})~",
std::string(jsonify(environment)),
cmd,
entrypoint).get()).get();
write = os::write(
path::join(layerPath, "json"),
stringify(manifest));
if (write.isError()) {
return Failure("Failed to save docker test image layer '" + layerId +
"': " + write.error());
}
const std::string rootfsDir = path::join(layerPath, "layer");
mkdir = os::mkdir(rootfsDir);
if (mkdir.isError()) {
return Failure("Failed to create layer rootfs directory '" +
rootfsDir + "': " + mkdir.error());
}
// Create one linux rootfs for the layer.
Try<process::Owned<Rootfs>> rootfs = LinuxRootfs::create(rootfsDir);
if (rootfs.isError()) {
return Failure("Failed to create docker test image rootfs: " +
rootfs.error());
}
Future<Nothing> tarRootfs = command::tar(
Path("."),
Path(path::join(layerPath, "layer.tar")),
rootfsDir);
tarRootfs.await();
if (!tarRootfs.isReady()) {
return Failure(
"Failed to tar root filesystem: " +
(tarRootfs.isFailed() ? tarRootfs.failure() : "discarded"));
}
Try<Nothing> rmdir = os::rmdir(rootfsDir);
if (rmdir.isError()) {
return Failure("Failed to remove layer rootfs directory: " +
rmdir.error());
}
write = os::write(
path::join(layerPath, "VERSION"),
"1.0");
if (write.isError()) {
return Failure("Failed to save layer version: " + write.error());
}
Future<Nothing> tarImage = command::tar(
Path("."),
Path(path::join(directory, name + ".tar")),
imagePath);
tarImage.await();
if (!tarImage.isReady()) {
return Failure(
"Failed to tar docker test image: " +
(tarImage.isFailed() ? tarImage.failure() : "discarded"));
}
rmdir = os::rmdir(imagePath);
if (rmdir.isError()) {
return Failure("Failed to remove image directory: " +
rmdir.error());
}
return Nothing();
}
};
} // namespace tests {
} // namespace internal {
} // namespace mesos {
#endif // __TEST_DOCKER_ARCHIVE_HPP__