test: Improve containerized test mechanism

Aim is to ensure that code coverage information can escape the
container.  To do this:
 - Enter a new mount namespace too, so that we can...
 - Bind mount the expected source directory into the container
 - Share memory with the sub-process so coverage information is
   shared too.
This commit is contained in:
David Drysdale 2016-01-11 10:37:29 +00:00
parent b644412a66
commit ff1f7a1c3c
2 changed files with 44 additions and 17 deletions

View File

@ -1,5 +1,8 @@
#include "ares-test.h"
#ifdef HAVE_CONTAINER
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
@ -10,15 +13,13 @@
#include <sstream>
#include <vector>
#ifdef HAVE_CONTAINER
namespace ares {
namespace test {
namespace {
struct ContainerInfo {
std::string dirname_;
ContainerFilesystem* fs_;
std::string hostname_;
std::string domainname_;
VoidToIntFn fn_;
@ -29,7 +30,7 @@ int EnterContainer(void *data) {
if (verbose) {
std::cerr << "Running function in container {chroot='"
<< container->dirname_ << "', hostname='" << container->hostname_
<< container->fs_->root() << "', hostname='" << container->hostname_
<< "', domainname='" << container->domainname_ << "'}"
<< std::endl;
}
@ -44,9 +45,22 @@ int EnterContainer(void *data) {
std::cerr << "Child in user namespace has uid " << getuid() << std::endl;
return -1;
}
if (!container->fs_->mountpt().empty()) {
// We want to bind mount this inside the specified directory.
std::string innerdir = container->fs_->root() + container->fs_->mountpt();
if (verbose) std::cerr << " mount --bind " << container->fs_->mountpt()
<< " " << innerdir << std::endl;
int rc = mount(container->fs_->mountpt().c_str(), innerdir.c_str(),
"none", MS_BIND, 0);
if (rc != 0) {
std::cerr << "Warning: failed to bind mount " << container->fs_->mountpt() << " at "
<< innerdir << ", errno=" << errno << std::endl;
}
}
// Move into the specified directory.
if (chdir(container->dirname_.c_str()) != 0) {
std::cerr << "Failed to chdir('" << container->dirname_
if (chdir(container->fs_->root().c_str()) != 0) {
std::cerr << "Failed to chdir('" << container->fs_->root()
<< "'), errno=" << errno << std::endl;
return -1;
}
@ -89,17 +103,18 @@ int EnterContainer(void *data) {
// - chroot()ed into a particular directory
// - having a specified hostname/domainname
int RunInContainer(const std::string& dirname, const std::string& hostname,
int RunInContainer(ContainerFilesystem* fs, const std::string& hostname,
const std::string& domainname, VoidToIntFn fn) {
const int stack_size = 1024 * 1024;
std::vector<byte> stack(stack_size, 0);
ContainerInfo container = {dirname, hostname, domainname, fn};
ContainerInfo container = {fs, hostname, domainname, fn};
// Start a child process in a new user and UTS namespace
pid_t child = clone(EnterContainer, stack.data() + stack_size,
CLONE_NEWUSER|CLONE_NEWUTS|SIGCHLD, (void *)&container);
CLONE_VM|CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWUTS|SIGCHLD,
(void *)&container);
if (child < 0) {
std::cerr << "Failed to clone()" << std::endl;
std::cerr << "Failed to clone(), errno=" << errno << std::endl;
return -1;
}
@ -135,7 +150,7 @@ int RunInContainer(const std::string& dirname, const std::string& hostname,
return status;
}
ContainerFilesystem::ContainerFilesystem(NameContentList files) {
ContainerFilesystem::ContainerFilesystem(NameContentList files, const std::string& mountpt) {
rootdir_ = TempNam(nullptr, "ares-chroot");
mkdir(rootdir_.c_str(), 0755);
dirs_.push_front(rootdir_);
@ -147,6 +162,14 @@ ContainerFilesystem::ContainerFilesystem(NameContentList files) {
files_.push_back(std::unique_ptr<TransientFile>(
new TransientFile(fullpath, nc.second)));
}
if (!mountpt.empty()) {
char buffer[PATH_MAX + 1];
if (realpath(mountpt.c_str(), buffer)) {
mountpt_ = buffer;
std::string fullpath = rootdir_ + mountpt_;
EnsureDirExists(fullpath);
}
}
}
ContainerFilesystem::~ContainerFilesystem() {

View File

@ -337,26 +337,30 @@ class EnvValue {
};
#endif
#ifdef HAVE_CONTAINER
// Linux-specific functionality for running code in a container, implemented
// in ares-test-ns.cc
#ifdef HAVE_CONTAINER
typedef std::function<int(void)> VoidToIntFn;
typedef std::vector<std::pair<std::string, std::string>> NameContentList;
int RunInContainer(const std::string& dirname, const std::string& hostname,
const std::string& domainname, VoidToIntFn fn);
class ContainerFilesystem {
public:
explicit ContainerFilesystem(NameContentList files);
ContainerFilesystem(NameContentList files, const std::string& mountpt);
~ContainerFilesystem();
std::string root() const { return rootdir_; };
std::string mountpt() const { return mountpt_; };
private:
void EnsureDirExists(const std::string& dir);
std::string rootdir_;
std::string mountpt_;
std::list<std::string> dirs_;
std::vector<std::unique_ptr<TransientFile>> files_;
};
int RunInContainer(ContainerFilesystem* fs, const std::string& hostname,
const std::string& domainname, VoidToIntFn fn);
#define ICLASS_NAME(casename, testname) Contained##casename##_##testname
#define CONTAINED_TEST_F(casename, testname, hostname, domainname, files) \
class ICLASS_NAME(casename, testname) : public casename { \
@ -365,9 +369,9 @@ class ContainerFilesystem {
static int InnerTestBody(); \
}; \
TEST_F(ICLASS_NAME(casename, testname), _) { \
ContainerFilesystem chroot(files); \
ContainerFilesystem chroot(files, ".."); \
VoidToIntFn fn(ICLASS_NAME(casename, testname)::InnerTestBody); \
EXPECT_EQ(0, RunInContainer(chroot.root(), hostname, domainname, fn)); \
EXPECT_EQ(0, RunInContainer(&chroot, hostname, domainname, fn)); \
} \
int ICLASS_NAME(casename, testname)::InnerTestBody()