| // 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 __WINDOWS__ |
| #include <sys/wait.h> |
| #endif // __WINDOWS__ |
| |
| #include <string.h> |
| #ifndef __WINDOWS__ |
| #include <unistd.h> |
| #endif // __WINDOWS__ |
| |
| #include <list> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| #include <checks/checker_process.hpp> |
| |
| #include "docker/docker.hpp" |
| |
| #include <process/defer.hpp> |
| #include <process/gmock.hpp> |
| #include <process/gtest.hpp> |
| #include <process/owned.hpp> |
| |
| #include <stout/check.hpp> |
| #include <stout/error.hpp> |
| #include <stout/exit.hpp> |
| #include <stout/lambda.hpp> |
| #include <stout/none.hpp> |
| #include <stout/option.hpp> |
| #include <stout/os.hpp> |
| #include <stout/path.hpp> |
| #include <stout/result.hpp> |
| #include <stout/stringify.hpp> |
| #include <stout/strings.hpp> |
| |
| #include <stout/os/exec.hpp> |
| #include <stout/os/exists.hpp> |
| #include <stout/os/pstree.hpp> |
| #include <stout/os/shell.hpp> |
| #include <stout/os/temp.hpp> |
| #include <stout/os/which.hpp> |
| |
| #ifdef __linux__ |
| #include "linux/cgroups.hpp" |
| #include "linux/fs.hpp" |
| #include "linux/perf.hpp" |
| #endif |
| |
| #ifdef ENABLE_PORT_MAPPING_ISOLATOR |
| #include "linux/routing/utils.hpp" |
| #endif |
| |
| #include "logging/logging.hpp" |
| |
| #include "tests/environment.hpp" |
| #include "tests/flags.hpp" |
| #include "tests/utils.hpp" |
| |
| #ifdef ENABLE_PORT_MAPPING_ISOLATOR |
| using namespace routing; |
| #endif |
| |
| using std::list; |
| using std::set; |
| using std::string; |
| using std::vector; |
| |
| using process::Failure; |
| using process::Future; |
| using process::Owned; |
| |
| using stout::internal::tests::TestFilter; |
| |
| |
| namespace mesos { |
| namespace internal { |
| namespace tests { |
| |
| // Storage for the global environment instance. |
| Environment* environment; |
| |
| |
| class BenchmarkFilter : public TestFilter |
| { |
| public: |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "BENCHMARK_") && !flags.benchmark; |
| } |
| }; |
| |
| |
| class CfsFilter : public TestFilter |
| { |
| public: |
| CfsFilter() |
| { |
| #ifdef __linux__ |
| Result<string> hierarchy = cgroups::hierarchy("cpu"); |
| if (hierarchy.isSome()) { |
| bool cfsQuotaEnabled = |
| os::exists(path::join(hierarchy.get(), "cpu.cfs_quota_us")); |
| |
| if (cfsQuotaEnabled) { |
| cfsError = None(); |
| } else { |
| cfsError = Error("CFS bandwidth control is not available"); |
| } |
| } else if (hierarchy.isError()) { |
| cfsError = Error( |
| "There was an error finding the 'cpu' cgroup hierarchy:\n" + |
| hierarchy.error()); |
| } else { |
| cfsError = Error( |
| "The 'cpu' cgroup hierarchy was not found, which means\n" |
| "that CFS bandwidth control is not available"); |
| } |
| |
| if (cfsError.isSome()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "The 'CFS_' tests cannot be run because:\n" |
| << cfsError->message << "\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| #else |
| cfsError = Error( |
| "These tests require CFS bandwidth control, which is a " |
| "Linux kernel feature, but Linux has not been detected"); |
| #endif // __linux__ |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "CFS_") && cfsError.isSome(); |
| } |
| |
| private: |
| Option<Error> cfsError; |
| }; |
| |
| |
| class CgroupsFilter : public TestFilter |
| { |
| public: |
| CgroupsFilter() |
| { |
| #ifdef __linux__ |
| Try<set<string>> hierarchies = cgroups::hierarchies(); |
| if (hierarchies.isError()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any cgroups tests that require mounting\n" |
| << "hierarchies because reading cgroup heirarchies failed:\n" |
| << hierarchies.error() << "\n" |
| << "We'll disable the CgroupsNoHierarchyTest test fixture for now.\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| |
| error = hierarchies.error(); |
| } else if (!hierarchies->empty()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any cgroups tests that require mounting\n" |
| << "hierarchies because you have the following hierarchies mounted:\n" |
| << strings::trim(stringify(hierarchies.get()), " {},") << "\n" |
| << "We'll disable the CgroupsNoHierarchyTest test fixture for now.\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| |
| error = Error("Hierarchies exist"); |
| } |
| #endif // __linux__ |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| if (matches(test, "CGROUPS_") || matches(test, "Cgroups")) { |
| #ifdef __linux__ |
| Result<string> user = os::user(); |
| CHECK_SOME(user); |
| |
| if (matches(test, "NOHIERARCHY_")) { |
| return user.get() != "root" || |
| !cgroups::enabled() || |
| error.isSome(); |
| } |
| |
| return user.get() != "root" || !cgroups::enabled(); |
| #else |
| return true; |
| #endif // __linux__ |
| } |
| |
| return false; |
| } |
| |
| private: |
| Option<Error> error; |
| }; |
| |
| |
| class CurlFilter : public TestFilter |
| { |
| public: |
| CurlFilter() |
| { |
| #ifndef __WINDOWS__ |
| curlError = os::which("curl").isNone(); |
| #else |
| // NOTE: We cannot use `os::which` here because it specifically checks the |
| // `PATH` for `curl`, but on Windows, we rely on `curl` being placed by the |
| // build next to the other executables (e.g. `mesos-agent` and |
| // `mesos-tests`). When placed like this, `::CreateProcess` is guaranteed to |
| // find it, regardless of `PATH` (and likewise `os::which`). Because it is |
| // built and placed as a build dependency of `mesos-agent`, it will always |
| // be available for testing. |
| curlError = false; |
| #endif // __WINDOWS__ |
| if (curlError) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "No 'curl' command found so no 'curl' tests will be run\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "CURL_") && curlError; |
| } |
| |
| private: |
| bool curlError; |
| }; |
| |
| |
| class NvidiaGpuFilter : public TestFilter |
| { |
| public: |
| NvidiaGpuFilter() |
| { |
| #ifndef ENABLE_NVML |
| nvidiaGpuError = true; |
| |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "Linking against libnvml is disabled so\n" |
| << " no Nvidia GPU tests will be run\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| #else |
| nvidiaGpuError = os::which("nvidia-smi").isNone(); |
| |
| if (nvidiaGpuError) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "No 'nvidia-smi' command found so no Nvidia GPU tests will run\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| #endif // ENABLE_NVML |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "NVIDIA_GPU_") && nvidiaGpuError; |
| } |
| |
| private: |
| bool nvidiaGpuError; |
| }; |
| |
| |
| class DockerFilter : public TestFilter |
| { |
| public: |
| DockerFilter() |
| { |
| #if defined(__linux__) || defined(__WINDOWS__) |
| Try<Owned<Docker>> docker = Docker::create( |
| flags.docker, |
| flags.docker_socket); |
| |
| if (!docker.isError()) { |
| Try<Nothing> version = docker.get()->validateVersion(Version(1, 9, 0)); |
| if (version.isError()) { |
| dockerUserNetworkError = version.error(); |
| } |
| } else { |
| dockerError = docker.error(); |
| } |
| |
| #ifdef __WINDOWS__ |
| // On Windows, the ability to enter another container's namespace was |
| // enabled on newer Windows builds (>=1709). So, check if we can do |
| // this to run the docker health check tests. |
| LOG(WARNING) << "Testing shared container network namespaces on Windows. " |
| << "This might take up to 30 seconds..."; |
| |
| if (dockerError.isNone() && dockerUserNetworkError.isNone()) { |
| dockerNamespaceError = runNetNamespaceCheck(docker.get()); |
| } |
| #endif // __WINDOWS__ |
| #else |
| dockerError = Error("Docker tests are not supported on this platform"); |
| #endif // __linux__ || __WINDOWS__ |
| |
| if (dockerError.isSome()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any Docker tests because:\n" |
| << dockerError->message << "\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| |
| if (dockerUserNetworkError.isSome()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any Docker user network tests because:\n" |
| << dockerUserNetworkError->message << "\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| |
| if (dockerNamespaceError.isSome()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any Docker network health checks tests because:\n" |
| << dockerNamespaceError->message << "\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| if (dockerError.isSome()) { |
| return matches(test, "DOCKER_"); |
| } |
| |
| if (dockerUserNetworkError.isSome()) { |
| return matches(test, "DOCKER_USERNETWORK_"); |
| } |
| |
| return matches(test, "DOCKER_") && |
| matches(test, "NETNAMESPACE_") && |
| dockerNamespaceError.isSome(); |
| } |
| |
| private: |
| #ifdef __WINDOWS__ |
| Future<Nothing> launchContainer( |
| const Owned<Docker>& docker, |
| const string& containerName, |
| const string& networkName, |
| const string& imageName) |
| { |
| Docker::RunOptions opts; |
| opts.privileged = false; |
| opts.name = containerName; |
| opts.network = networkName; |
| opts.additionalOptions = {"-d", "--rm"}; |
| opts.image = imageName; |
| opts.arguments = {"ping", "-n", "60", "127.0.0.1"}; |
| |
| // Launches the container in detached mode, which means that docker |
| // run should return as soon as the container successfully launched. |
| return docker->run(opts, process::Subprocess::PATH(os::DEV_NULL)) |
| .then([=](const Option<int>& status) -> Future<Nothing> { |
| if (!status.isSome()) { |
| return Failure( |
| "Container " + containerName + " failed with unknown exit code"); |
| } |
| |
| if (status.get() != 0) { |
| return Failure( |
| "Container " + containerName + " returned exit code " + |
| stringify(status.get())); |
| } |
| |
| return Nothing(); |
| }); |
| } |
| |
| Option<Error> runNetNamespaceCheck(const Owned<Docker>& docker) |
| { |
| const string image = |
| string(mesos::internal::checks::DOCKER_HEALTH_CHECK_IMAGE); |
| |
| // Use `os::system` here because `docker->inspect()` only works on |
| // containers even though `docker inspect` cli command works on images. |
| const Option<int> res = os::system(strings::join( |
| " ", |
| docker->getPath(), |
| "-H", |
| docker->getSocket(), |
| "inspect", |
| image, |
| "> NUL")); |
| |
| if (res != 0) { |
| return Error("Cannot find " + image); |
| } |
| |
| // Launch two containers. One with regular network settings and the |
| // other with "--network=container:<ID>" to enter the first container's |
| // namespace. |
| const string container1 = id::UUID::random().toString(); |
| const string container2 = id::UUID::random().toString(); |
| |
| Future<Nothing> containers = |
| launchContainer(docker, container1, "nat", image) |
| .then(process::defer([=]() { |
| return launchContainer( |
| docker, container2, "container:" + container1, image) |
| .then(lambda::bind(&Docker::rm, docker, container2, true)) |
| .onAny(lambda::bind(&Docker::rm, docker, container1, true)); |
| })); |
| |
| // A minute should be enough for both containers to lauch and delete. |
| containers.await(Minutes(1)); |
| |
| if (containers.isFailed()) { |
| return Error("Failed to launch containers: " + containers.failure()); |
| } else if (!containers.isReady()) { |
| return Error("Container launch timed out"); |
| } |
| |
| return None(); |
| } |
| #endif // __WINDOWS__ |
| |
| Option<Error> dockerError; |
| Option<Error> dockerUserNetworkError; |
| Option<Error> dockerNamespaceError; |
| }; |
| |
| |
| // Note: This is a temporary filter to disable tests that use |
| // overlay as backend on filesystems where `d_type` support is |
| // missing. In particular, many XFS nodes are known to have this |
| // issue due to mkfs option with `f_type = 0`. Please see |
| // MESOS-8121 for more info. |
| // |
| // This filter assumes that the affected tests will use |
| // `/tmp` as root directory of the agent's work dir. |
| class DtypeFilter : public TestFilter |
| { |
| public: |
| DtypeFilter() |
| { |
| #ifdef __linux__ |
| auto checkDirDtype = [this](const string& directory) { |
| string probeDir = path::join(directory, ".probe"); |
| |
| Try<Nothing> mkdir = os::mkdir(probeDir); |
| if (mkdir.isError()) { |
| dtypeError = Error( |
| "Cannot verify filesystem d_type attribute: " |
| "Failed to create temporary directory '" + |
| probeDir + "': " + mkdir.error()); |
| } |
| |
| Try<bool> supportDType = fs::dtypeSupported(directory); |
| |
| // Clean up the temporary directory that is used |
| // for d_type detection. |
| Try<Nothing> rmdir = os::rmdir(probeDir); |
| if (rmdir.isError()) { |
| LOG(WARNING) << "Failed to remove temporary directory" |
| << "' " << probeDir << "': " << rmdir.error(); |
| } |
| |
| if (supportDType.isError()) { |
| dtypeError = Error( |
| "Cannot verify filesystem d_type attribute: " + |
| supportDType.error()); |
| } |
| |
| if (!supportDType.get()) { |
| dtypeError = Error( |
| "The underlying filesystem of " + directory + |
| " misses d_type support."); |
| } |
| }; |
| |
| // TODO(mzhu): Avoid hard coding a specific directory for |
| // filtering. This is a temporary solution. |
| checkDirDtype(os::temp()); |
| |
| if (dtypeError.isSome()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any overlay backend tests because:\n" |
| << dtypeError->message << "\n" |
| << "-------------------------------------------------------------\n"; |
| return; |
| } |
| #endif |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return dtypeError.isSome() && matches(test, "DTYPE_"); |
| } |
| |
| private: |
| Option<Error> dtypeError; |
| }; |
| |
| |
| class InternetFilter : public TestFilter |
| { |
| public: |
| InternetFilter() |
| { |
| #ifdef __WINDOWS__ |
| error = os::system("ping -n 1 -w 1000 google.com") != 0; |
| #else |
| error = os::system("ping -c 1 -W 1 google.com") != 0; |
| #endif // __WINDOWS__ |
| if (error) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any INTERNET tests because no internet access\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "INTERNET_") && error; |
| } |
| |
| private: |
| bool error; |
| }; |
| |
| |
| class IPTablesFilter : public TestFilter |
| { |
| public: |
| IPTablesFilter() |
| { |
| #ifdef __linux__ |
| // Check iptables -w option |
| // |
| if (os::which("iptables").isNone()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "No 'iptables' command found so no tests depending\n" |
| << "on 'iptables' will be run\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| |
| iptablesError = Error("iptables command not found"); |
| return; |
| } |
| |
| if (::geteuid() != 0) { |
| iptablesError = Error("iptables command requires root"); |
| return; |
| } |
| |
| Try<string> iptables = os::shell("iptables -w -n -L OUTPUT"); |
| if (iptables.isError()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "'iptables' command does not support '-w' option\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| |
| iptablesError = Error("iptables command does not support -w option"); |
| return; |
| } |
| #else |
| iptablesError = Error("Unsupported platform"); |
| #endif |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return iptablesError.isSome() && matches(test, "IPTABLES_"); |
| } |
| |
| private: |
| Option<Error> iptablesError; |
| }; |
| |
| |
| class LogrotateFilter : public TestFilter |
| { |
| public: |
| LogrotateFilter() |
| { |
| logrotateError = os::which("logrotate").isNone(); |
| if (logrotateError) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "No 'logrotate' command found so no 'logrotate' tests\n" |
| << "will be run\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "LOGROTATE_") && logrotateError; |
| } |
| |
| private: |
| bool logrotateError; |
| }; |
| |
| |
| class NetcatFilter : public TestFilter |
| { |
| public: |
| NetcatFilter() |
| { |
| netcatError = os::which("nc").isNone(); |
| if (netcatError) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "No 'nc' command found so no tests depending on 'nc' will run\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "NC_") && netcatError; |
| } |
| |
| private: |
| bool netcatError; |
| }; |
| |
| |
| // This filter enables tests for the cgroups/net_cls isolator after |
| // checking that net_cls cgroup subsystem has been enabled on the |
| // system. We cannot rely on the generic cgroups test filter |
| // ('CgroupsFilter') to enable the cgroups/net_cls isolator tests |
| // since, although cgroups might be enabled on a linux distribution |
| // the net_cls subsystem is not turned on, by default, on all |
| // distributions. |
| class NetClsCgroupsFilter : public TestFilter |
| { |
| public: |
| NetClsCgroupsFilter() |
| { |
| netClsError = true; |
| #ifdef __linux__ |
| Try<bool> netCls = cgroups::enabled("net_cls"); |
| |
| if (netCls.isError()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "Cannot enable net_cls cgroup subsystem associated test cases \n" |
| << "since we cannot determine the existence of the net_cls cgroup\n" |
| << "subsystem.\n" |
| << "-------------------------------------------------------------\n"; |
| return; |
| } |
| |
| if (netCls.isSome() && !netCls.get()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "Cannot enable net_cls cgroup subsystem associated test cases \n" |
| << "since net_cls cgroup subsystem is not enabled. Check instructions\n" |
| << "for your linux distrubtion to enable the net_cls cgroup subsystem\n" |
| << "on your system.\n" |
| << "-----------------------------------------------------------\n"; |
| return; |
| } |
| netClsError = false; |
| #else |
| std::cerr |
| << "-----------------------------------------------------------\n" |
| << "Cannot enable net_cls cgroup subsystem associated test cases\n" |
| << "since this platform does not support cgroups.\n" |
| << "-----------------------------------------------------------\n"; |
| #endif |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "NET_CLS_") && netClsError; |
| } |
| |
| private: |
| bool netClsError; |
| }; |
| |
| |
| class NetworkIsolatorTestFilter : public TestFilter |
| { |
| public: |
| NetworkIsolatorTestFilter() |
| { |
| #ifdef ENABLE_PORT_MAPPING_ISOLATOR |
| Try<Nothing> check = routing::check(); |
| if (check.isError()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any PortMapping tests because:\n" |
| << check.error() << "\n" |
| << "-------------------------------------------------------------\n"; |
| |
| portMappingError = Error(check.error()); |
| } |
| #endif |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| if (matches(test, "PortMappingIsolatorTest") || |
| matches(test, "PortMappingMesosTest")) { |
| #ifdef ENABLE_PORT_MAPPING_ISOLATOR |
| return !portMappingError.isNone(); |
| #else |
| return true; |
| #endif |
| } |
| |
| return false; |
| } |
| |
| private: |
| Option<Error> portMappingError; |
| }; |
| |
| |
| class SupportedFilesystemTestFilter : public TestFilter |
| { |
| public: |
| explicit SupportedFilesystemTestFilter(const string& fsname) |
| { |
| #ifdef __linux__ |
| Try<bool> check = fs::supported(fsname); |
| if (check.isError()) { |
| fsSupportError = check.error(); |
| } else if (!check.get()) { |
| fsSupportError = Error(fsname + " is not supported on your systems"); |
| } |
| #else |
| fsSupportError = |
| Error(fsname + " tests not supported on non-Linux systems"); |
| #endif |
| |
| if (fsSupportError.isSome()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any " << fsname << " tests because:\n" |
| << fsSupportError->message << "\n" |
| << "-------------------------------------------------------------\n"; |
| } |
| } |
| |
| Option<Error> fsSupportError; |
| }; |
| |
| |
| class AufsFilter : public SupportedFilesystemTestFilter |
| { |
| public: |
| AufsFilter() : SupportedFilesystemTestFilter("aufs") {} |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return fsSupportError.isSome() && matches(test, "AUFS_"); |
| } |
| }; |
| |
| |
| class OverlayFSFilter : public SupportedFilesystemTestFilter |
| { |
| public: |
| OverlayFSFilter() : SupportedFilesystemTestFilter("overlayfs") {} |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return fsSupportError.isSome() && matches(test, "OVERLAYFS_"); |
| } |
| }; |
| |
| |
| class XfsFilter : public SupportedFilesystemTestFilter |
| { |
| public: |
| XfsFilter() : SupportedFilesystemTestFilter("xfs") {} |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return fsSupportError.isSome() && matches(test, "XFS_"); |
| } |
| }; |
| |
| |
| class PerfCPUCyclesFilter : public TestFilter |
| { |
| public: |
| PerfCPUCyclesFilter() |
| { |
| #ifdef __linux__ |
| bool perfUnavailable = !perf::supported(); |
| if (perfUnavailable) { |
| perfError = Error( |
| "Could not find the 'perf' command or its version lower that " |
| "2.6.39 so tests using it to sample the 'cpu-cycles' hardware " |
| "event will not be run."); |
| } else { |
| bool cyclesUnavailable = |
| os::system("perf list hw | grep cpu-cycles >/dev/null") != 0; |
| if (cyclesUnavailable) { |
| perfError = Error( |
| "The 'cpu-cycles' hardware event of 'perf' is not available on\n" |
| "this platform so tests using it will not be run.\n" |
| "One likely reason is that the tests are run in a virtual\n" |
| "machine that does not provide CPU performance counters"); |
| } |
| } |
| #else |
| perfError = Error("Tests using 'perf' cannot be run on non-Linux systems"); |
| #endif // __linux__ |
| |
| if (perfError.isSome()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << perfError->message << "\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| // Disable all tests that try to sample 'cpu-cycles' events using 'perf'. |
| return (matches(test, "ROOT_CGROUPS_PERF_PerfTest") || |
| matches(test, "ROOT_CGROUPS_PERF_UserCgroup") || |
| matches(test, "ROOT_CGROUPS_PERF_RollForward") || |
| matches(test, "ROOT_CGROUPS_PERF_Sample")) && perfError.isSome(); |
| } |
| |
| private: |
| Option<Error> perfError; |
| }; |
| |
| |
| class PerfFilter : public TestFilter |
| { |
| public: |
| PerfFilter() |
| { |
| #ifdef __linux__ |
| perfError = !perf::supported(); |
| if (perfError) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "require 'perf' version >= 2.6.39 so no 'perf' tests will be run\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| #else |
| perfError = true; |
| #endif // __linux__ |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "PERF_") && perfError; |
| } |
| |
| private: |
| bool perfError; |
| }; |
| |
| |
| class RootFilter : public TestFilter |
| { |
| public: |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| #ifdef __WINDOWS__ |
| // On Windows, tests are expected to be run as Administrator. |
| return false; |
| #else |
| Result<string> user = os::user(); |
| CHECK_SOME(user); |
| |
| #ifdef __linux__ |
| // On Linux non-privileged users are limited to 64k of locked |
| // memory so we cannot run the MemIsolatorTest.Usage. |
| if (matches(test, "MemIsolatorTest")) { |
| return user.get() != "root"; |
| } |
| #endif // __linux__ |
| |
| return matches(test, "ROOT_") && user.get() != "root"; |
| #endif // __WINDOWS__ |
| } |
| }; |
| |
| |
| class SeccompFilter : public TestFilter |
| { |
| public: |
| SeccompFilter() |
| { |
| // Since the Seccomp parser depends on CPU architecture, we can run Seccomp |
| // tests deterministically only on `x86_64`. |
| Try<string> arch = os::shell("arch"); |
| if (arch.isError()) { |
| seccompError = arch.error(); |
| } else if (strings::trim(arch.get()) != "x86_64") { |
| seccompError = Error("Seccomp tests cannot be run on non-x86_64 systems"); |
| } |
| |
| if (seccompError.isSome()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We can't run any SECCOMP tests:\n" |
| << seccompError.get() << "\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "SECCOMP_") && seccompError.isSome(); |
| } |
| |
| private: |
| Option<Error> seccompError; |
| }; |
| |
| |
| class UnprivilegedUserFilter : public TestFilter |
| { |
| public: |
| UnprivilegedUserFilter() |
| { |
| #ifdef __WINDOWS__ |
| unprivilegedUserFound = false; |
| #else |
| Option<string> user = os::getenv("SUDO_USER"); |
| if (user.isNone() || user.get() == "root") { |
| unprivilegedUserFound = false; |
| } else { |
| unprivilegedUserFound = true; |
| } |
| |
| if (!unprivilegedUserFound) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "No usable unprivileged user found from the 'SUDO_USER'\n" |
| << "environment variable. So tests that rely on an unprivileged\n" |
| << "user will not run\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| #endif |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "UNPRIVILEGED_USER_") && !unprivilegedUserFound; |
| } |
| |
| private: |
| bool unprivilegedUserFound; |
| }; |
| |
| |
| class UnzipFilter : public TestFilter |
| { |
| public: |
| UnzipFilter() |
| { |
| unzipError = os::which("unzip").isNone(); |
| if (unzipError) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "No 'unzip' command found so no 'unzip' tests will be run\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "UNZIP_") && unzipError; |
| } |
| |
| private: |
| bool unzipError; |
| }; |
| |
| |
| // This is a test filter for the veth CNI plugin. |
| class VEthFilter : public TestFilter |
| { |
| public: |
| VEthFilter() |
| { |
| #ifdef __linux__ |
| vector<string> messages; |
| |
| // Checking if it runs as root. |
| Result<string> user = os::user(); |
| CHECK_SOME(user); |
| |
| if (user.get() != "root") { |
| messages.emplace_back("non-root user"); |
| } |
| |
| // This command returns `ip utility, iproute2-YYMMDD` where |
| // `YYMMDD` is a release (snapshot) date of iproute2. |
| Try<string> ipVersion = os::shell("ip -Version"); |
| |
| // Checking if iproute2 exists. |
| if (ipVersion.isError()) { |
| messages.emplace_back("iproute2 not found"); |
| } else { |
| // Checking if it supports `ip link set ... netns ...`. |
| const string version = strings::trim(ipVersion.get()); |
| if (version.size() < 6) { |
| messages.emplace_back("unexpected version"); |
| } else { |
| Try<int> snapshot = numify<int>(version.substr(version.size() - 6)); |
| if (snapshot.isError()) { |
| messages.emplace_back("iproute2 version is not an integer"); |
| } else if (snapshot.get() < 100224) { |
| // Support for `netns` was added to iproute2 in v2.6.33. |
| messages.emplace_back("iproute2 doesn't support network namespaces"); |
| } |
| } |
| } |
| |
| // Checking if libprocess is bound on loopback address, in that |
| // case network namespace with veth network won't be able to |
| // connect to parent process on host network namespace. |
| // TODO(urbanserj): Improve the network connectivity check. |
| process::network::inet::Address address = process::address(); |
| if (address.ip.isLoopback()) { |
| messages.emplace_back("libprocess is bound on loopback address"); |
| } |
| |
| disabled = !messages.empty(); |
| if (disabled) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We can't run any VETH tests:\n" |
| << strings::join("\n", messages) << "\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| } |
| #else |
| disabled = true; |
| #endif // __linux__ |
| } |
| |
| bool disable(const ::testing::TestInfo* test) const override |
| { |
| return matches(test, "VETH_") && disabled; |
| } |
| |
| private: |
| bool disabled; |
| }; |
| |
| |
| Environment::Environment(const Flags& _flags) |
| : stout::internal::tests::Environment( |
| std::vector<std::shared_ptr<TestFilter>>{ |
| std::make_shared<AufsFilter>(), |
| std::make_shared<BenchmarkFilter>(), |
| std::make_shared<CfsFilter>(), |
| std::make_shared<CgroupsFilter>(), |
| std::make_shared<CurlFilter>(), |
| std::make_shared<DockerFilter>(), |
| std::make_shared<DtypeFilter>(), |
| std::make_shared<InternetFilter>(), |
| std::make_shared<IPTablesFilter>(), |
| std::make_shared<LogrotateFilter>(), |
| std::make_shared<NetcatFilter>(), |
| std::make_shared<NetClsCgroupsFilter>(), |
| std::make_shared<NetworkIsolatorTestFilter>(), |
| std::make_shared<NvidiaGpuFilter>(), |
| std::make_shared<OverlayFSFilter>(), |
| std::make_shared<PerfCPUCyclesFilter>(), |
| std::make_shared<PerfFilter>(), |
| std::make_shared<RootFilter>(), |
| std::make_shared<SeccompFilter>(), |
| std::make_shared<UnprivilegedUserFilter>(), |
| std::make_shared<UnzipFilter>(), |
| std::make_shared<VEthFilter>(), |
| std::make_shared<XfsFilter>()}), |
| flags(_flags) |
| { |
| // Add our test event listeners. |
| ::testing::TestEventListeners& listeners = |
| ::testing::UnitTest::GetInstance()->listeners(); |
| |
| listeners.Append(process::FilterTestEventListener::instance()); |
| listeners.Append(process::ClockTestEventListener::instance()); |
| |
| // Add the temporary directory event listener which will clean up |
| // any directories created after each test finishes. |
| temporaryDirectoryEventListener = new TemporaryDirectoryEventListener(); |
| listeners.Append(temporaryDirectoryEventListener); |
| } |
| |
| |
| void Environment::SetUp() |
| { |
| // Clear any MESOS_ environment variables so they don't affect our tests. |
| foreachkey (const string& key, os::environment()) { |
| if (key.find("MESOS_") == 0) { |
| os::unsetenv(key); |
| } |
| } |
| |
| // Set the path to the native JNI library for running JVM tests. |
| // TODO(tillt): Adapt library towards JNI specific name once libmesos |
| // has been split. |
| if (os::getenv("MESOS_NATIVE_JAVA_LIBRARY").isNone()) { |
| string path = getLibMesosPath(); |
| os::setenv("MESOS_NATIVE_JAVA_LIBRARY", path); |
| } |
| |
| #if !GTEST_IS_THREADSAFE |
| EXIT(EXIT_FAILURE) << "Testing environment is not thread safe, bailing!"; |
| #endif // !GTEST_IS_THREADSAFE |
| } |
| |
| |
| void Environment::TearDown() |
| { |
| // Make sure we haven't left any child processes lying around. |
| // TODO(benh): Look for processes in the same group or session that |
| // might have been reparented. |
| // TODO(jmlvanre): Consider doing this `OnTestEnd` in a listener so |
| // that we can identify leaked processes more precisely. |
| Try<os::ProcessTree> pstree = os::pstree(0); |
| |
| if (pstree.isSome() && !pstree->children.empty()) { |
| FAIL() << "Tests completed with child processes remaining:\n" |
| << pstree.get(); |
| } |
| } |
| |
| |
| Try<string> Environment::mkdtemp() |
| { |
| return temporaryDirectoryEventListener->mkdtemp(); |
| } |
| |
| |
| void tests::Environment::TemporaryDirectoryEventListener::OnTestEnd( |
| const testing::TestInfo&) |
| { |
| foreach (const string& directory, directories) { |
| #ifdef __linux__ |
| // Try to remove any mounts under 'directory'. |
| if (::geteuid() == 0) { |
| Try<Nothing> unmount = fs::unmountAll(directory, MNT_DETACH); |
| if (unmount.isError()) { |
| LOG(ERROR) << "Failed to umount for directory '" << directory |
| << "': " << unmount.error(); |
| } |
| } |
| #endif |
| |
| Try<Nothing> rmdir = os::rmdir(directory); |
| if (rmdir.isError()) { |
| LOG(ERROR) << "Failed to remove '" << directory |
| << "': " << rmdir.error(); |
| } |
| } |
| |
| directories.clear(); |
| } |
| |
| |
| Try<string> Environment::TemporaryDirectoryEventListener::mkdtemp() |
| { |
| const ::testing::TestInfo* const testInfo = |
| ::testing::UnitTest::GetInstance()->current_test_info(); |
| |
| if (testInfo == nullptr) { |
| return Error("Failed to determine the current test information"); |
| } |
| |
| // We replace any slashes present in the test names (e.g. TYPED_TEST), |
| // to make sure the temporary directory resides under '/tmp/'. |
| const string& testCase = |
| strings::replace(testInfo->test_case_name(), "/", "_"); |
| |
| string testName = strings::replace(testInfo->name(), "/", "_"); |
| |
| // Adjust the test name to remove any 'DISABLED_' prefix (to make |
| // things easier to read). While this might seem alarming, if we are |
| // "running" a disabled test it must be the case that the test was |
| // explicitly enabled (e.g., via 'gtest_filter'). |
| if (strings::startsWith(testName, "DISABLED_")) { |
| testName = strings::remove(testName, "DISABLED_", strings::PREFIX); |
| } |
| |
| const string tmpdir = os::temp(); |
| |
| const string& path = |
| #ifndef __WINDOWS__ |
| path::join(tmpdir, strings::join("_", testCase, testName, "XXXXXX")); |
| #else |
| // TODO(hausdorff): When we resolve MESOS-5849, we should change |
| // this back to the same path as the Unix version. This is |
| // currently necessary to make the sandbox path short enough to |
| // avoid the infamous Windows path length errors, which would |
| // normally cause many of our tests to fail. |
| path::join(tmpdir, "XXXXXX"); |
| #endif // __WINDOWS__ |
| |
| Try<string> mkdtemp = os::mkdtemp(path); |
| if (mkdtemp.isSome()) { |
| directories.push_back(mkdtemp.get()); |
| } |
| return mkdtemp; |
| } |
| |
| } // namespace tests { |
| } // namespace internal { |
| } // namespace mesos { |