| /** |
| * 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. |
| */ |
| |
| #include <string> |
| |
| #include <stout/check.hpp> |
| #include <stout/foreach.hpp> |
| #include <stout/json.hpp> |
| #include <stout/os.hpp> |
| #include <stout/path.hpp> |
| #include <stout/result.hpp> |
| #include <stout/stringify.hpp> |
| #include <stout/uuid.hpp> |
| |
| #include "authorizer/authorizer.hpp" |
| |
| #ifdef __linux__ |
| #include "linux/cgroups.hpp" |
| #endif |
| |
| #ifdef WITH_NETWORK_ISOLATOR |
| #include "linux/routing/utils.hpp" |
| #endif |
| |
| #include "slave/constants.hpp" |
| #include "slave/containerizer/containerizer.hpp" |
| |
| #include "slave/containerizer/mesos/containerizer.hpp" |
| |
| #include "tests/containerizer.hpp" |
| #include "tests/environment.hpp" |
| #include "tests/flags.hpp" |
| #include "tests/mesos.hpp" |
| |
| using std::string; |
| |
| using namespace process; |
| |
| #ifdef WITH_NETWORK_ISOLATOR |
| using namespace routing; |
| #endif |
| |
| namespace mesos { |
| namespace internal { |
| namespace tests { |
| |
| #ifdef MESOS_HAS_JAVA |
| ZooKeeperTestServer* MesosZooKeeperTest::server = NULL; |
| Option<zookeeper::URL> MesosZooKeeperTest::url; |
| #endif // MESOS_HAS_JAVA |
| |
| MesosTest::MesosTest(const Option<zookeeper::URL>& url) : cluster(url) {} |
| |
| |
| void MesosTest::TearDown() |
| { |
| TemporaryDirectoryTest::TearDown(); |
| |
| // TODO(benh): Fail the test if shutdown hasn't been called? |
| Shutdown(); |
| } |
| |
| |
| master::Flags MesosTest::CreateMasterFlags() |
| { |
| master::Flags flags; |
| |
| // We use the current working directory from TempDirectoryTest |
| // to ensure the work directory remains the same within a test. |
| flags.work_dir = path::join(os::getcwd(), "master"); |
| |
| CHECK_SOME(os::mkdir(flags.work_dir.get())); |
| |
| flags.authenticate_frameworks = true; |
| flags.authenticate_slaves = true; |
| |
| // Create a default credentials file. |
| const string& path = path::join(os::getcwd(), "credentials"); |
| |
| Try<int> fd = os::open( |
| path, |
| O_WRONLY | O_CREAT | O_TRUNC, |
| S_IRUSR | S_IWUSR | S_IRGRP); |
| |
| CHECK_SOME(fd); |
| |
| // JSON default format for credentials |
| Credentials credentials; |
| Credential* credential = credentials.add_credentials(); |
| credential->set_principal(DEFAULT_CREDENTIAL.principal()); |
| credential->set_secret(DEFAULT_CREDENTIAL.secret()); |
| |
| CHECK_SOME(os::write(fd.get(), stringify(JSON::Protobuf(credentials)))) |
| << "Failed to write credentials to '" << path << "'"; |
| CHECK_SOME(os::close(fd.get())); |
| |
| flags.credentials = "file://" + path; |
| |
| // Set default ACLs. |
| flags.acls = ACLs(); |
| |
| // Use the replicated log (without ZooKeeper) by default. |
| flags.registry = "replicated_log"; |
| flags.registry_strict = true; |
| |
| // On many test VMs, this default is too small. |
| flags.registry_store_timeout = flags.registry_store_timeout * 5; |
| |
| return flags; |
| } |
| |
| |
| slave::Flags MesosTest::CreateSlaveFlags() |
| { |
| slave::Flags flags; |
| |
| // Create a temporary work directory (removed by Environment). |
| Try<string> directory = environment->mkdtemp(); |
| CHECK_SOME(directory) << "Failed to create temporary directory"; |
| |
| flags.work_dir = directory.get(); |
| |
| flags.launcher_dir = path::join(tests::flags.build_dir, "src"); |
| |
| // Create a default credential file. |
| const string& path = path::join(directory.get(), "credential"); |
| |
| Try<int> fd = os::open( |
| path, |
| O_WRONLY | O_CREAT | O_TRUNC, |
| S_IRUSR | S_IWUSR | S_IRGRP); |
| |
| CHECK_SOME(fd); |
| |
| Credential credential; |
| credential.set_principal(DEFAULT_CREDENTIAL.principal()); |
| credential.set_secret(DEFAULT_CREDENTIAL.secret()); |
| |
| CHECK_SOME(os::write(fd.get(), stringify(JSON::Protobuf(credential)))) |
| << "Failed to write slave credential to '" << path << "'"; |
| |
| CHECK_SOME(os::close(fd.get())); |
| |
| flags.credential = "file://" + path; |
| |
| // TODO(vinod): Consider making this true and fixing the tests. |
| flags.checkpoint = false; |
| |
| flags.resources = "cpus:2;mem:1024;disk:1024;ports:[31000-32000]"; |
| |
| flags.registration_backoff_factor = Milliseconds(10); |
| |
| // Make sure that the slave uses the same 'docker' as the tests. |
| flags.docker = tests::flags.docker; |
| |
| return flags; |
| } |
| |
| |
| Try<process::PID<master::Master> > MesosTest::StartMaster( |
| const Option<master::Flags>& flags) |
| { |
| return cluster.masters.start( |
| flags.isNone() ? CreateMasterFlags() : flags.get()); |
| } |
| |
| |
| Try<process::PID<master::Master> > MesosTest::StartMaster( |
| master::allocator::AllocatorProcess* allocator, |
| const Option<master::Flags>& flags) |
| { |
| return cluster.masters.start( |
| allocator, flags.isNone() ? CreateMasterFlags() : flags.get()); |
| } |
| |
| |
| Try<process::PID<master::Master> > MesosTest::StartMaster( |
| Authorizer* authorizer, |
| const Option<master::Flags>& flags) |
| { |
| return cluster.masters.start( |
| authorizer, flags.isNone() ? CreateMasterFlags() : flags.get()); |
| } |
| |
| |
| Try<process::PID<slave::Slave> > MesosTest::StartSlave( |
| const Option<slave::Flags>& flags) |
| { |
| return cluster.slaves.start( |
| flags.isNone() ? CreateSlaveFlags() : flags.get()); |
| } |
| |
| |
| Try<process::PID<slave::Slave> > MesosTest::StartSlave( |
| MockExecutor* executor, |
| const Option<slave::Flags>& flags) |
| { |
| slave::Containerizer* containerizer = new TestContainerizer(executor); |
| |
| Try<process::PID<slave::Slave> > pid = StartSlave(containerizer, flags); |
| |
| if (pid.isError()) { |
| delete containerizer; |
| return pid; |
| } |
| |
| containerizers[pid.get()] = containerizer; |
| |
| return pid; |
| } |
| |
| |
| Try<process::PID<slave::Slave> > MesosTest::StartSlave( |
| slave::Containerizer* containerizer, |
| const Option<slave::Flags>& flags) |
| { |
| return cluster.slaves.start( |
| containerizer, flags.isNone() ? CreateSlaveFlags() : flags.get()); |
| } |
| |
| |
| Try<process::PID<slave::Slave> > MesosTest::StartSlave( |
| slave::Containerizer* containerizer, |
| MasterDetector* detector, |
| const Option<slave::Flags>& flags) |
| { |
| return cluster.slaves.start( |
| containerizer, |
| detector, |
| flags.isNone() ? CreateSlaveFlags() : flags.get()); |
| } |
| |
| |
| Try<PID<slave::Slave> > MesosTest::StartSlave( |
| MasterDetector* detector, |
| const Option<slave::Flags>& flags) |
| { |
| return cluster.slaves.start( |
| detector, flags.isNone() ? CreateSlaveFlags() : flags.get()); |
| } |
| |
| |
| Try<PID<slave::Slave> > MesosTest::StartSlave( |
| MockExecutor* executor, |
| MasterDetector* detector, |
| const Option<slave::Flags>& flags) |
| { |
| slave::Containerizer* containerizer = new TestContainerizer(executor); |
| |
| Try<process::PID<slave::Slave> > pid = cluster.slaves.start( |
| containerizer, |
| detector, |
| flags.isNone() ? CreateSlaveFlags() : flags.get()); |
| |
| if (pid.isError()) { |
| delete containerizer; |
| return pid; |
| } |
| |
| containerizers[pid.get()] = containerizer; |
| |
| return pid; |
| } |
| |
| |
| void MesosTest::Stop(const process::PID<master::Master>& pid) |
| { |
| cluster.masters.stop(pid); |
| } |
| |
| |
| void MesosTest::Stop(const process::PID<slave::Slave>& pid, bool shutdown) |
| { |
| cluster.slaves.stop(pid, shutdown); |
| if (containerizers.count(pid) > 0) { |
| slave::Containerizer* containerizer = containerizers[pid]; |
| containerizers.erase(pid); |
| delete containerizer; |
| } |
| } |
| |
| |
| void MesosTest::Shutdown() |
| { |
| ShutdownMasters(); |
| ShutdownSlaves(); |
| } |
| |
| |
| void MesosTest::ShutdownMasters() |
| { |
| cluster.masters.shutdown(); |
| } |
| |
| |
| void MesosTest::ShutdownSlaves() |
| { |
| cluster.slaves.shutdown(); |
| |
| foreachvalue (slave::Containerizer* containerizer, containerizers) { |
| delete containerizer; |
| } |
| containerizers.clear(); |
| } |
| |
| |
| slave::Flags ContainerizerTest<slave::MesosContainerizer>::CreateSlaveFlags() |
| { |
| slave::Flags flags = MesosTest::CreateSlaveFlags(); |
| |
| #ifdef __linux__ |
| Result<string> user = os::user(); |
| EXPECT_SOME(user); |
| |
| // Use cgroup isolators if they're available and we're root. |
| // TODO(idownes): Refactor the cgroups/non-cgroups code. |
| if (cgroups::enabled() && user.get() == "root") { |
| flags.isolation = "cgroups/cpu,cgroups/mem"; |
| flags.cgroups_hierarchy = baseHierarchy; |
| flags.cgroups_root = TEST_CGROUPS_ROOT + "_" + UUID::random().toString(); |
| |
| // Enable putting the slave into memory and cpuacct cgroups. |
| flags.slave_subsystems = "memory,cpuacct"; |
| } else { |
| flags.isolation = "posix/cpu,posix/mem"; |
| } |
| #else |
| flags.isolation = "posix/cpu,posix/mem"; |
| #endif |
| |
| #ifdef WITH_NETWORK_ISOLATOR |
| if (user.get() == "root" && routing::check().isSome()) { |
| flags.isolation = strings::join( |
| ",", |
| flags.isolation, |
| "network/port_mapping"); |
| |
| // NOTE: By default, Linux sets host ip local port range to |
| // [32768, 61000]. We set 'ephemeral_ports' resource so that it |
| // does not overlap with the host ip local port range. |
| flags.resources = strings::join( |
| ";", |
| flags.resources.get(), |
| "ephemeral_ports:[30001-30999]"); |
| |
| // NOTE: '16' should be enough for all our tests. |
| flags.ephemeral_ports_per_container = 16; |
| } |
| #endif |
| |
| return flags; |
| } |
| |
| |
| #ifdef __linux__ |
| void ContainerizerTest<slave::MesosContainerizer>::SetUpTestCase() |
| { |
| Result<string> user = os::user(); |
| EXPECT_SOME(user); |
| |
| if (cgroups::enabled() && user.get() == "root") { |
| // Clean up any testing hierarchies. |
| Try<std::set<string> > hierarchies = cgroups::hierarchies(); |
| ASSERT_SOME(hierarchies); |
| foreach (const string& hierarchy, hierarchies.get()) { |
| if (strings::startsWith(hierarchy, TEST_CGROUPS_HIERARCHY)) { |
| AWAIT_READY(cgroups::cleanup(hierarchy)); |
| } |
| } |
| } |
| } |
| |
| |
| void ContainerizerTest<slave::MesosContainerizer>::TearDownTestCase() |
| { |
| Result<string> user = os::user(); |
| EXPECT_SOME(user); |
| |
| if (cgroups::enabled() && user.get() == "root") { |
| // Clean up any testing hierarchies. |
| Try<std::set<string> > hierarchies = cgroups::hierarchies(); |
| ASSERT_SOME(hierarchies); |
| foreach (const string& hierarchy, hierarchies.get()) { |
| if (strings::startsWith(hierarchy, TEST_CGROUPS_HIERARCHY)) { |
| AWAIT_READY(cgroups::cleanup(hierarchy)); |
| } |
| } |
| } |
| } |
| |
| |
| void ContainerizerTest<slave::MesosContainerizer>::SetUp() |
| { |
| MesosTest::SetUp(); |
| |
| subsystems.insert("cpu"); |
| subsystems.insert("cpuacct"); |
| subsystems.insert("memory"); |
| subsystems.insert("freezer"); |
| subsystems.insert("perf_event"); |
| |
| Result<string> user = os::user(); |
| EXPECT_SOME(user); |
| |
| if (cgroups::enabled() && user.get() == "root") { |
| foreach (const string& subsystem, subsystems) { |
| // Establish the base hierarchy if this is the first subsystem checked. |
| if (baseHierarchy.empty()) { |
| Result<string> hierarchy = cgroups::hierarchy(subsystem); |
| ASSERT_FALSE(hierarchy.isError()); |
| |
| if (hierarchy.isNone()) { |
| baseHierarchy = TEST_CGROUPS_HIERARCHY; |
| } else { |
| // Strip the subsystem to get the base hierarchy. |
| baseHierarchy = strings::remove( |
| hierarchy.get(), |
| subsystem, |
| strings::SUFFIX); |
| } |
| } |
| |
| // Mount the subsystem if necessary. |
| string hierarchy = path::join(baseHierarchy, subsystem); |
| Try<bool> mounted = cgroups::mounted(hierarchy, subsystem); |
| ASSERT_SOME(mounted); |
| if (!mounted.get()) { |
| ASSERT_SOME(cgroups::mount(hierarchy, subsystem)) |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any cgroups tests that require\n" |
| << "a hierarchy with subsystem '" << subsystem << "'\n" |
| << "because we failed to find an existing hierarchy\n" |
| << "or create a new one (tried '" << hierarchy << "').\n" |
| << "You can either remove all existing\n" |
| << "hierarchies, or disable this test case\n" |
| << "(i.e., --gtest_filter=-" |
| << ::testing::UnitTest::GetInstance() |
| ->current_test_info() |
| ->test_case_name() << ".*).\n" |
| << "-------------------------------------------------------------"; |
| } |
| } |
| } |
| } |
| |
| |
| void ContainerizerTest<slave::MesosContainerizer>::TearDown() |
| { |
| MesosTest::TearDown(); |
| |
| Result<string> user = os::user(); |
| EXPECT_SOME(user); |
| |
| if (cgroups::enabled() && user.get() == "root") { |
| foreach (const string& subsystem, subsystems) { |
| string hierarchy = path::join(baseHierarchy, subsystem); |
| |
| Try<std::vector<string> > cgroups = cgroups::get(hierarchy); |
| CHECK_SOME(cgroups); |
| |
| foreach (const string& cgroup, cgroups.get()) { |
| // Remove any cgroups that start with TEST_CGROUPS_ROOT |
| if (strings::startsWith(cgroup, TEST_CGROUPS_ROOT)) { |
| AWAIT_READY(cgroups::destroy(hierarchy, cgroup)); |
| } |
| } |
| } |
| } |
| } |
| #endif // __linux__ |
| |
| } // namespace tests { |
| } // namespace internal { |
| } // namespace mesos { |