blob: 66c7598f6e3e9eba711811583b6d1dc78905c122 [file] [log] [blame]
// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "germ/container_manager.h"
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <base/at_exit.h>
#include <base/run_loop.h>
#include <base/time/time.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "germ/container.h"
#include "germ/mock_germ_zygote.h"
#include "germ/test_util.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace germ {
namespace {
// Basic test of starting/terminating a container.
TEST(ContainerManagerTest, StartAndTerminate) {
base::AtExitManager exit_manager;
base::MessageLoopForIO message_loop;
MockGermZygote mock_zygote;
ContainerManager container_manager(&mock_zygote);
const char kContainerName[] = "test_container";
const pid_t kContainerPid = 1234;
soma::ContainerSpec spec = MakeSpecForTest(kContainerName);
{
InSequence seq;
EXPECT_CALL(mock_zygote, StartContainer(EqualsSpec(spec), _))
.WillOnce(DoAll(SetArgPointee<1>(kContainerPid), Return(true)));
EXPECT_CALL(mock_zygote, Kill(kContainerPid, SIGTERM))
.WillOnce(Return(true));
EXPECT_CALL(mock_zygote, Kill(kContainerPid, SIGKILL))
.WillOnce(Return(true));
}
// Start the container.
EXPECT_TRUE(container_manager.StartContainer(spec));
scoped_refptr<Container> container = container_manager.Lookup(kContainerName);
ASSERT_TRUE(container != nullptr);
EXPECT_EQ(kContainerName, container->name());
EXPECT_EQ(Container::State::RUNNING, container->state());
EXPECT_EQ(Container::State::RUNNING, container->desired_state());
EXPECT_EQ(kContainerPid, container->init_pid());
// Terminate the container.
EXPECT_TRUE(
container_manager.TerminateContainer(kContainerName, base::TimeDelta()));
EXPECT_EQ(Container::State::STOPPED, container->desired_state());
// TerminateContainer should have queued a task on the message loop to send
// SIGKILL to the container, so run it.
base::RunLoop run_loop;
run_loop.RunUntilIdle();
EXPECT_EQ(Container::State::DYING, container->state());
// Notify container_manager that the container process has been reaped.
siginfo_t info = {};
info.si_pid = kContainerPid;
info.si_signo = SIGCHLD;
info.si_status = SIGKILL;
info.si_code = CLD_KILLED;
container_manager.OnReap(info);
// The container should no longer be managed by ContainerManager.
EXPECT_TRUE(container_manager.Lookup(kContainerName) == nullptr);
}
// Test that containers are restarted if they die on their own.
TEST(ContainerManagerTest, RestartContainer) {
base::AtExitManager exit_manager;
base::MessageLoopForIO message_loop;
MockGermZygote mock_zygote;
ContainerManager container_manager(&mock_zygote);
const char kContainerName[] = "test_container";
const pid_t kContainerPid = 1234;
const pid_t kRestartedContainerPid = 5678;
soma::ContainerSpec spec = MakeSpecForTest(kContainerName);
EXPECT_CALL(mock_zygote, StartContainer(EqualsSpec(spec), _))
.Times(2)
.WillOnce(DoAll(SetArgPointee<1>(kContainerPid), Return(true)))
.WillOnce(DoAll(SetArgPointee<1>(kRestartedContainerPid), Return(true)));
// Start the container.
EXPECT_TRUE(container_manager.StartContainer(spec));
scoped_refptr<Container> container = container_manager.Lookup(kContainerName);
ASSERT_TRUE(container != nullptr);
EXPECT_EQ(kContainerName, container->name());
EXPECT_EQ(Container::State::RUNNING, container->state());
EXPECT_EQ(Container::State::RUNNING, container->desired_state());
EXPECT_EQ(kContainerPid, container->init_pid());
// Notify container_manager that the container exited.
siginfo_t info = {};
info.si_pid = kContainerPid;
info.si_signo = SIGCHLD;
info.si_status = 0;
info.si_code = CLD_EXITED;
container_manager.OnReap(info);
// ContainerManager should have restarted the container.
container = container_manager.Lookup(kContainerName);
ASSERT_TRUE(container != nullptr);
EXPECT_EQ(kContainerName, container->name());
EXPECT_EQ(Container::State::RUNNING, container->state());
EXPECT_EQ(Container::State::RUNNING, container->desired_state());
EXPECT_EQ(kRestartedContainerPid, container->init_pid());
}
// StartContainer on an already running container should restart the container.
TEST(ContainerManagerTest, StartContainerAlreadyRunning) {
base::AtExitManager exit_manager;
base::MessageLoopForIO message_loop;
MockGermZygote mock_zygote;
ContainerManager container_manager(&mock_zygote);
const char kContainerName[] = "test_container";
const pid_t kContainerPid = 1234;
const pid_t kRestartedContainerPid = 5678;
soma::ContainerSpec spec = MakeSpecForTest(kContainerName);
{
InSequence seq;
EXPECT_CALL(mock_zygote, StartContainer(EqualsSpec(spec), _))
.WillOnce(DoAll(SetArgPointee<1>(kContainerPid), Return(true)));
EXPECT_CALL(mock_zygote, Kill(kContainerPid, SIGTERM))
.WillOnce(Return(true));
EXPECT_CALL(mock_zygote, Kill(kContainerPid, SIGKILL))
.WillOnce(Return(true));
EXPECT_CALL(mock_zygote, StartContainer(EqualsSpec(spec), _))
.WillOnce(
DoAll(SetArgPointee<1>(kRestartedContainerPid), Return(true)));
}
// Start the container.
EXPECT_TRUE(container_manager.StartContainer(spec));
scoped_refptr<Container> container = container_manager.Lookup(kContainerName);
ASSERT_TRUE(container != nullptr);
EXPECT_EQ(kContainerName, container->name());
EXPECT_EQ(Container::State::RUNNING, container->state());
EXPECT_EQ(Container::State::RUNNING, container->desired_state());
EXPECT_EQ(kContainerPid, container->init_pid());
// Call StartContainer on the already running container.
EXPECT_TRUE(container_manager.StartContainer(spec));
// StartContainer should attempt to kill the running container first.
EXPECT_EQ(Container::State::DYING, container->state());
EXPECT_EQ(Container::State::RUNNING, container->desired_state());
// When killing the running container, we should have queued a task on the
// message loop to send SIGKILL to the container, so run it.
base::RunLoop run_loop;
run_loop.RunUntilIdle();
// Notify container_manager that the original container process has been
// reaped.
siginfo_t info = {};
info.si_pid = kContainerPid;
info.si_signo = SIGCHLD;
info.si_status = SIGKILL;
info.si_code = CLD_KILLED;
container_manager.OnReap(info);
// ContainerManager should have restarted the container.
container = container_manager.Lookup(kContainerName);
ASSERT_TRUE(container != nullptr);
EXPECT_EQ(kContainerName, container->name());
EXPECT_EQ(Container::State::RUNNING, container->state());
EXPECT_EQ(Container::State::RUNNING, container->desired_state());
EXPECT_EQ(kRestartedContainerPid, container->init_pid());
}
} // namespace
} // namespace germ