| // 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 <sched.h> |
| #include <unistd.h> |
| |
| #include <linux/sched.h> |
| |
| #include <vector> |
| |
| #include <process/collect.hpp> |
| |
| #include <stout/abort.hpp> |
| #include <stout/check.hpp> |
| #include <stout/hashset.hpp> |
| #include <stout/os.hpp> |
| #include <stout/path.hpp> |
| #include <stout/strings.hpp> |
| #include <stout/stringify.hpp> |
| |
| #include "linux/cgroups.hpp" |
| #include "linux/cgroups2.hpp" |
| #include "linux/ns.hpp" |
| #include "linux/systemd.hpp" |
| |
| #include "mesos/resources.hpp" |
| |
| #include "slave/containerizer/mesos/linux_launcher.hpp" |
| #include "slave/containerizer/mesos/paths.hpp" |
| |
| using namespace process; |
| |
| using lambda::function; |
| using std::map; |
| using std::set; |
| using std::string; |
| using std::vector; |
| |
| using mesos::slave::ContainerState; |
| |
| namespace mesos { |
| namespace internal { |
| namespace slave { |
| |
| // Launcher for Linux systems with cgroups. Uses a freezer cgroup to |
| // track pids. |
| class LinuxLauncherProcess : public Process<LinuxLauncherProcess> |
| { |
| public: |
| LinuxLauncherProcess( |
| const Flags& flags, |
| bool cgroupsV2, |
| const Option<string>& freezerHierarchy, |
| const Option<string>& systemdHierarchy); |
| |
| virtual Future<hashset<ContainerID>> recover( |
| const vector<mesos::slave::ContainerState>& states); |
| |
| virtual Try<pid_t> fork( |
| const ContainerID& containerId, |
| const string& path, |
| const vector<string>& argv, |
| const mesos::slave::ContainerIO& containerIO, |
| const flags::FlagsBase* flags, |
| const Option<map<string, string>>& environment, |
| const Option<int>& enterNamespaces, |
| const Option<int>& cloneNamespaces, |
| const vector<int_fd>& whitelistFds); |
| |
| virtual Future<Nothing> destroy(const ContainerID& containerId); |
| |
| virtual Future<ContainerStatus> status( |
| const ContainerID& containerId); |
| |
| private: |
| // Helper struct for storing information about each container. A |
| // "container" here means a cgroup in the freezer subsystem that is |
| // used to represent a collection of processes. This container may |
| // also have multiple namespaces associated with it but that is not |
| // managed explicitly here. |
| struct Container |
| { |
| ContainerID id; |
| |
| // NOTE: this represents the "init" of the container that we |
| // created (it may be for an executor, or any arbitrary process |
| // that has been launched in the event of nested containers). |
| // |
| // This is none when it's an orphan container (i.e., we have a |
| // freezer cgroup but we were not expecting the container during |
| // `LinuxLauncher::recover`). |
| Option<pid_t> pid = None(); |
| }; |
| |
| Try<Nothing> recoverContainersFromCgroups(); |
| Try<Nothing> recoverContainersFromCgroups2(); |
| |
| Future<Nothing> destroyCgroups(const Container& container); |
| Future<Nothing> _destroyCgroups(const Container& container); |
| Future<Nothing> destroyCgroups2(const Container& container); |
| |
| const Flags flags; |
| |
| struct CgroupsInfo |
| { |
| // Flag indicating whether cgroups v2 is used. |
| bool v2; |
| |
| // Absolute path of the cgroup freezer hierarchy. |
| // Only present when cgroups v1 is used. |
| Option<string> freezerHierarchy; |
| |
| // Absolute path the systemd hierarchy. |
| // Only present in cgroups v1 when systemd is enabled |
| // (i.e. `systemd::enabled()`). |
| Option<string> systemdHierarchy; |
| } cgroupsInfo; |
| |
| hashmap<ContainerID, Container> containers; |
| }; |
| |
| |
| Try<Launcher*> LinuxLauncher::create(const Flags& flags) |
| { |
| Try<bool> mounted = cgroups2::mounted(); |
| if (mounted.isError()) { |
| return Error("Failed to check if cgroups2 is mounted: " + mounted.error()); |
| } |
| if (*mounted) { |
| return createCgroups2Launcher(flags); |
| } |
| return createCgroupsLauncher(flags); |
| } |
| |
| |
| Try<Launcher*> LinuxLauncher::createCgroupsLauncher(const Flags& flags) |
| { |
| Try<string> freezerHierarchy = cgroups::prepare( |
| flags.cgroups_hierarchy, |
| "freezer", |
| flags.cgroups_root); |
| |
| if (freezerHierarchy.isError()) { |
| return Error( |
| "Failed to create Linux launcher: " + freezerHierarchy.error()); |
| } |
| |
| // Ensure that no other subsystem is attached to the freezer hierarchy. |
| Try<set<string>> subsystems = cgroups::subsystems(*freezerHierarchy); |
| if (subsystems.isError()) { |
| return Error( |
| "Failed to get the list of attached subsystems for hierarchy " + |
| *freezerHierarchy); |
| } else if (subsystems->size() != 1) { |
| return Error( |
| "Unexpected subsystems found attached to the hierarchy " + |
| *freezerHierarchy); |
| } |
| |
| LOG(INFO) << "Using " << *freezerHierarchy |
| << " as the freezer hierarchy for the Linux launcher"; |
| |
| // On systemd environments, we currently do the following: |
| // |
| // (1) For Mesos version < 1.6, we migrate executor pids into a |
| // separate slice. This allows the life-time of the executor to be |
| // extended past the life-time of the slave. See MESOS-3352. |
| // |
| // (2) For Mesos version >= 1.6, we create the cgroups under the |
| // systemd hierarchy directly, and use the same layout as that in |
| // the freezer hierarchy. This should prevent systemd from migrating |
| // pids in other hierarchies as well because the pids are unknown to |
| // systemd. |
| Option<string> systemdHierarchy; |
| |
| if (systemd::enabled()) { |
| systemdHierarchy = systemd::hierarchy(); |
| |
| // Create the root cgroup if does not exist. |
| if (!cgroups::exists(*systemdHierarchy, flags.cgroups_root)) { |
| Try<Nothing> create = cgroups::create( |
| *systemdHierarchy, |
| flags.cgroups_root); |
| |
| if (create.isError()) { |
| return Error( |
| "Failed to create cgroup root under systemd hierarchy: " + |
| create.error()); |
| } |
| } |
| |
| LOG(INFO) << "Using " << *systemdHierarchy |
| << " as the systemd hierarchy for the Linux launcher"; |
| } |
| |
| return new LinuxLauncher(flags, false, *freezerHierarchy, systemdHierarchy); |
| } |
| |
| |
| Try<Launcher*> LinuxLauncher::createCgroups2Launcher(const Flags& flags) |
| { |
| return new LinuxLauncher(flags, true, None(), None()); |
| } |
| |
| |
| bool LinuxLauncher::available() |
| { |
| bool available = false; |
| |
| // Check if cgroups v2 is available. |
| Try<bool> mounted = cgroups2::mounted(); |
| available |= mounted.isSome() && *mounted; |
| |
| // Check if cgroups v1 is available. |
| // Make sure: |
| // 1. Are running as root. |
| // 2. 'freezer' subsystem is enabled. |
| Try<bool> freezer = cgroups::enabled("freezer"); |
| available |= ::geteuid() == 0 && freezer.isSome() && *freezer; |
| |
| return available; |
| } |
| |
| |
| LinuxLauncher::LinuxLauncher( |
| const Flags& flags, |
| bool cgroupsV2, |
| const Option<string>& freezerHierarchy, |
| const Option<string>& systemdHierarchy) |
| : process(new LinuxLauncherProcess( |
| flags, cgroupsV2, freezerHierarchy, systemdHierarchy)) |
| { |
| process::spawn(process.get()); |
| } |
| |
| |
| LinuxLauncher::~LinuxLauncher() |
| { |
| process::terminate(process.get()); |
| process::wait(process.get()); |
| } |
| |
| |
| Future<hashset<ContainerID>> LinuxLauncher::recover( |
| const vector<mesos::slave::ContainerState>& states) |
| { |
| return dispatch(process.get(), &LinuxLauncherProcess::recover, states); |
| } |
| |
| |
| Try<pid_t> LinuxLauncher::fork( |
| const ContainerID& containerId, |
| const string& path, |
| const vector<string>& argv, |
| const mesos::slave::ContainerIO& containerIO, |
| const flags::FlagsBase* flags, |
| const Option<map<string, string>>& environment, |
| const Option<int>& enterNamespaces, |
| const Option<int>& cloneNamespaces, |
| const vector<int_fd>& whitelistFds) |
| { |
| return dispatch( |
| process.get(), |
| &LinuxLauncherProcess::fork, |
| containerId, |
| path, |
| argv, |
| containerIO, |
| flags, |
| environment, |
| enterNamespaces, |
| cloneNamespaces, |
| whitelistFds).get(); |
| } |
| |
| |
| Future<Nothing> LinuxLauncher::destroy(const ContainerID& containerId) |
| { |
| return dispatch(process.get(), &LinuxLauncherProcess::destroy, containerId); |
| } |
| |
| |
| Future<ContainerStatus> LinuxLauncher::status( |
| const ContainerID& containerId) |
| { |
| return dispatch(process.get(), &LinuxLauncherProcess::status, containerId); |
| } |
| |
| |
| LinuxLauncherProcess::LinuxLauncherProcess( |
| const Flags& flags, |
| bool cgroupsV2, |
| const Option<string>& freezerHierarchy, |
| const Option<string>& systemdHierarchy) |
| : flags(flags) |
| { |
| cgroupsInfo.v2 = cgroupsV2; |
| cgroupsInfo.freezerHierarchy = freezerHierarchy; |
| cgroupsInfo.systemdHierarchy = systemdHierarchy; |
| } |
| |
| |
| Future<hashset<ContainerID>> LinuxLauncherProcess::recover( |
| const vector<ContainerState>& states) |
| { |
| // 1. Cgroups v1: We recover container ids by looking at the cgroups in the |
| // freezer or (optional) systemd hierarchy. |
| // Cgroups v2: We recover container ids by looking at the cgroups in the |
| // `flags.cgroups_root` directory, in `/sys/fs/cgroup`. |
| // |
| // 2. Create a list of the container ids that we expect to recover |
| // based on the persisted `ContainerState`s. If a container is expected |
| // to be recovered but was not found while parsing the cgroups, we create |
| // a container for it from the `ContainerState`, so |
| // `LinuxLauncher::destroy` doesn't fail when called for this container. |
| // |
| // 3. Cgroups v1: In a systemd environment, check that the container pids are |
| // being managed by either the Mesos `systemd` cgroup or the |
| // systemd `MESOS_EXECUTORS_SLICE`. |
| // |
| // 4. Return a list of "orphan" containers, that is, containers that were |
| // recovered but were not expected to be recovered. |
| LOG(INFO) << "Recovering Linux launcher"; |
| |
| // Recover containers by looking at the cgroups found in the cgroups |
| // filesystem. |
| Try<Nothing> recover = cgroupsInfo.v2 |
| ? recoverContainersFromCgroups2() |
| : recoverContainersFromCgroups(); |
| |
| if (recover.isError()) { |
| return Failure("Failed to recover containers from the cgroup filesystem: " |
| + recover.error()); |
| } |
| |
| // Now loop through the containers expected by ContainerState so we |
| // can have a complete list of the containers we might ever want to |
| // destroy as well as be able to determine orphans below. |
| hashset<ContainerID> expected = {}; |
| |
| foreach (const ContainerState& state, states) { |
| expected.insert(state.container_id()); |
| |
| if (!containers.contains(state.container_id())) { |
| // Given that the container was not recovered, that implies that |
| // the container was previously destroyed. |
| // |
| // We still add it to `containers` so that when `LinuxLauncher::destroy` |
| // gets called for this container below we don't fail. |
| Container container; |
| container.id = state.container_id(); |
| container.pid = state.pid(); |
| |
| containers.put(container.id, container); |
| |
| LOG(INFO) << "Recovered (destroyed) container " << container.id; |
| } else { |
| // This container exists, so we save the pid so we can check |
| // that it's part of the systemd "Mesos executor slice" below. |
| containers[state.container_id()].pid = state.pid(); |
| } |
| } |
| |
| if (!cgroupsInfo.v2) { |
| // TODO(benh): In the past we used to make sure that we didn't have |
| // multiple containers that had the same pid. This seemed pretty |
| // random, and is highly unlikely to occur in practice. That being |
| // said, a good sanity check we could do here is to make sure that |
| // the pid is actually contained within each container's freezer |
| // cgroup. |
| |
| // If we are on a systemd environment, check that container pids are |
| // either in the `MESOS_EXECUTORS_SLICE`, or under Mesos cgroup root |
| // under the systemd hierarchy. If they are not, warn the operator |
| // that resource isolation may be invalidated. |
| // |
| // TODO(jmlvanre): Add a flag that enforces this rather than just |
| // logs a warning (i.e., we exit if a pid was found in the freezer |
| // but not in the `MESOS_EXECUTORS_SLICE` or Mesos cgroup root under |
| // the systemd hierarhcy). We need a flag to support the upgrade |
| // path. |
| if (cgroupsInfo.systemdHierarchy.isSome()) { |
| foreachvalue (const Container& container, containers) { |
| if (container.pid.isNone()) { |
| continue; |
| } |
| |
| pid_t pid = container.pid.get(); |
| |
| // No need to proceed the check if the pid does not exist |
| // anymore. This is possible if the container terminates when |
| // the agent is down. |
| if (os::kill(pid, 0) != 0) { |
| continue; |
| } |
| |
| Result<string> cgroup = cgroups::named::cgroup("systemd", pid); |
| if (!cgroup.isSome()) { |
| LOG(ERROR) << "Failed to get cgroup in systemd hierarchy for " |
| << "container pid " << pid << ": " |
| << (cgroup.isError() ? cgroup.error() : "Not found"); |
| continue; |
| } |
| |
| bool inMesosCgroupRoot = |
| strings::startsWith(*cgroup, flags.cgroups_root); |
| |
| bool inMesosExecutorSlice = |
| strings::contains(*cgroup, systemd::mesos::MESOS_EXECUTORS_SLICE); |
| |
| if (!inMesosCgroupRoot && !inMesosExecutorSlice) { |
| LOG(WARNING) |
| << "Couldn't find pid " << pid << " in either Mesos cgroup root '" |
| << flags.cgroups_root << "' under systemd hierarchy, or systemd " |
| << "slice '" << systemd::mesos::MESOS_EXECUTORS_SLICE << "'; " |
| << "This can lead to lack of proper resource isolation"; |
| } |
| } |
| } |
| } |
| |
| // Return the list of top-level AND nested orphaned containers, |
| // i.e., a container that we recovered but was not expected during |
| // recovery. |
| hashset<ContainerID> orphans = {}; |
| |
| foreachvalue (const Container& container, containers) { |
| if (!expected.contains(container.id)) { |
| LOG(INFO) << container.id << " is a known orphaned container"; |
| orphans.insert(container.id); |
| } |
| } |
| |
| return orphans; |
| } |
| |
| |
| Try<Nothing> LinuxLauncherProcess::recoverContainersFromCgroups() |
| { |
| // Recover all of the "containers" we know about based on the |
| // existing cgroups. Note that we check both the freezer hierarchy |
| // and the systemd hierarchy (if enabled), and combine the results. |
| hashset<string> cgroups; |
| |
| Try<vector<string>> freezerCgroups = |
| cgroups::get(*cgroupsInfo.freezerHierarchy, flags.cgroups_root); |
| |
| if (freezerCgroups.isError()) { |
| return Error( |
| "Failed to get cgroups from " |
| + path::join(*cgroupsInfo.freezerHierarchy, flags.cgroups_root) |
| + ": " + freezerCgroups.error()); |
| } |
| |
| foreach (const string& cgroup, freezerCgroups.get()) { |
| cgroups.insert(cgroup); |
| } |
| |
| if (cgroupsInfo.systemdHierarchy.isSome()) { |
| Try<vector<string>> systemdCgroups = |
| cgroups::get(*cgroupsInfo.systemdHierarchy, flags.cgroups_root); |
| |
| if (systemdCgroups.isError()) { |
| return Error( |
| "Failed to get cgroups from " + |
| path::join(*cgroupsInfo.systemdHierarchy, flags.cgroups_root) + |
| ": " + systemdCgroups.error()); |
| } |
| |
| foreach (const string& cgroup, systemdCgroups.get()) { |
| cgroups.insert(cgroup); |
| } |
| } |
| |
| foreach (const string& cgroup, cgroups) { |
| // Need to parse the cgroup to see if it's one we created (i.e., |
| // matches our separator structure) or one that someone else |
| // created (e.g., in the future we might have nested containers |
| // that are managed by something else rooted within the freezer |
| // hierarchy). |
| Option<ContainerID> containerId = |
| containerizer::paths::parseCgroupPath(flags.cgroups_root, cgroup); |
| |
| if (containerId.isNone()) { |
| LOG(INFO) << "Not recovering cgroup " << cgroup; |
| continue; |
| } |
| |
| Container container; |
| container.id = containerId.get(); |
| |
| // Add this to `containers` so when `destroy` gets called we |
| // properly destroy the container, even if we determine it's an |
| // orphan below. |
| containers.put(container.id, container); |
| |
| LOG(INFO) << "Recovered container " << container.id; |
| } |
| |
| return Nothing(); |
| } |
| |
| |
| Try<Nothing> LinuxLauncherProcess::recoverContainersFromCgroups2() |
| { |
| Try<set<string>> cgroups = cgroups2::get(flags.cgroups_root); |
| if (cgroups.isError()) { |
| return Error("Failed to get cgroups: " + cgroups.error()); |
| } |
| |
| foreach (const string& cgroup, *cgroups) { |
| // Parse the cgroups to see if we created them. Add the container ids |
| // of the cgroups that parse to `containers` so that on `destroy` they |
| // get properly disposed. |
| Option<ContainerID> containerId = |
| containerizer::paths::cgroups2::containerId(flags.cgroups_root, cgroup); |
| if (containerId.isNone()) { |
| continue; |
| } |
| |
| Container container; |
| container.id = *containerId; |
| |
| containers.put(container.id, container); |
| |
| LOG(INFO) << "Recovered container " << container.id; |
| } |
| return Nothing(); |
| } |
| |
| |
| Try<pid_t> LinuxLauncherProcess::fork( |
| const ContainerID& containerId, |
| const string& path, |
| const vector<string>& argv, |
| const mesos::slave::ContainerIO& containerIO, |
| const flags::FlagsBase* flags, |
| const Option<map<string, string>>& environment, |
| const Option<int>& enterNamespaces, |
| const Option<int>& cloneNamespaces, |
| const vector<int_fd>& whitelistFds) |
| { |
| // Make sure this container (nested or not) is unique. |
| if (containers.contains(containerId)) { |
| return Error("Container '" + stringify(containerId) + "' already exists"); |
| } |
| |
| Option<pid_t> parentPid = None(); |
| |
| // Ensure nested containers have known parents. |
| if (containerId.has_parent()) { |
| Option<Container> parent = containers.get(containerId.parent()); |
| if (parent.isNone()) { |
| return Error("Unknown parent container"); |
| } |
| |
| if (parent->pid.isNone()) { |
| // TODO(benh): Could also look up a pid in the container and use |
| // that in order to enter the namespaces? This would be best |
| // effort because we don't know the namespaces that had been |
| // created for the original pid. |
| return Error("Unknown parent container pid, can not enter namespaces"); |
| } |
| |
| parentPid = parent->pid; |
| } |
| |
| // Ensure we didn't pass `enterNamespaces` |
| // if we aren't forking a nested container. |
| if (!containerId.has_parent() && enterNamespaces.isSome()) { |
| return Error("Cannot enter parent namespaces for non-nested container"); |
| } |
| |
| int enterFlags = enterNamespaces.getOrElse(0); |
| int cloneFlags = cloneNamespaces.getOrElse(0); |
| |
| LOG(INFO) << "Launching " << (parentPid.isSome() ? "nested " : "") |
| << "container " << containerId << " and cloning with namespaces " |
| << ns::stringify(cloneFlags); |
| |
| cloneFlags |= SIGCHLD; // Specify SIGCHLD as child termination signal. |
| |
| vector<Subprocess::ParentHook> parentHooks; |
| if (!cgroupsInfo.v2) { |
| // The ordering of the hooks is: |
| // (1) Create the freezer cgroup, and add the child to the cgroup. |
| // (2) Create the systemd cgroup, and add the child to the cgroup. |
| // |
| // NOTE: The order is important here because the destroy code will |
| // always kill the container based on the pids in the freezer |
| // cgroup. The systemd cgroup will be removed after that. Therefore, |
| // we want to make sure that if the pid is in the systemd cgroup, it |
| // must be in the freezer cgroup. |
| |
| // Hook for creating and assigning the child into a freezer cgroup. |
| parentHooks.emplace_back(Subprocess::ParentHook([=](pid_t child) { |
| return cgroups::isolate( |
| *cgroupsInfo.freezerHierarchy, |
| containerizer::paths::getCgroupPath( |
| this->flags.cgroups_root, containerId), |
| child); |
| })); |
| |
| // Hook for creating and assigning the child into a systemd cgroup. |
| if (cgroupsInfo.systemdHierarchy.isSome()) { |
| parentHooks.emplace_back(Subprocess::ParentHook([=](pid_t child) { |
| return cgroups::isolate( |
| *cgroupsInfo.systemdHierarchy, |
| containerizer::paths::getCgroupPath( |
| this->flags.cgroups_root, containerId), |
| child); |
| })); |
| } |
| } |
| |
| vector<Subprocess::ChildHook> childHooks; |
| |
| // Create a new session id so termination signals from the parent process |
| // to not terminate the child. |
| childHooks.push_back(Subprocess::ChildHook::SETSID()); |
| |
| Try<Subprocess> child = subprocess( |
| path, |
| argv, |
| containerIO.in, |
| containerIO.out, |
| containerIO.err, |
| flags, |
| environment, |
| [parentPid, enterFlags, cloneFlags](const lambda::function<int()>& child) { |
| if (parentPid.isSome()) { |
| Try<pid_t> pid = ns::clone(*parentPid, enterFlags, child, cloneFlags); |
| if (pid.isError()) { |
| LOG(WARNING) << "Failed to enter namespaces and clone: " |
| << pid.error(); |
| return -1; |
| } |
| return *pid; |
| } else { |
| return os::clone(child, cloneFlags); |
| } |
| }, |
| parentHooks, |
| childHooks, |
| whitelistFds); |
| |
| if (child.isError()) { |
| return Error("Failed to clone child process: " + child.error()); |
| } |
| |
| Container container; |
| container.id = containerId; |
| container.pid = child->pid(); |
| |
| containers.put(container.id, container); |
| |
| return container.pid.get(); |
| } |
| |
| |
| Future<Nothing> LinuxLauncherProcess::destroy(const ContainerID& containerId) |
| { |
| LOG(INFO) << "Asked to destroy container " << containerId; |
| |
| Option<Container> container = containers.get(containerId); |
| |
| if (container.isNone()) { |
| return Nothing(); |
| } |
| |
| // Check if `container` has any nested containers. |
| foreachkey (const ContainerID& id, containers) { |
| if (id.has_parent()) { |
| if (container->id == id.parent()) { |
| return Failure("Container has nested containers"); |
| } |
| } |
| } |
| |
| return cgroupsInfo.v2 |
| ? destroyCgroups2(*container) |
| : destroyCgroups(*container); |
| } |
| |
| |
| Future<Nothing> LinuxLauncherProcess::destroyCgroups(const Container& container) |
| { |
| const string cgroup = |
| containerizer::paths::getCgroupPath(flags.cgroups_root, container.id); |
| |
| // We remove the container so that we don't attempt multiple |
| // destroys simultaneously and no other functions will return |
| // information about the container that is currently being (or has |
| // been) destroyed. This implies, however, that if the destroy fails |
| // the caller won't be able to retry because we won't know about the |
| // container anymore. |
| // |
| // NOTE: it's safe to use `container->id` from here on because it's |
| // a copy of the Container that we're about to delete. |
| containers.erase(container.id); |
| |
| // Determine if this is a partially destroyed container. A container |
| // is considered partially destroyed if we have recovered it from |
| // ContainerState but we don't have a freezer cgroup for it. If this |
| // is a partially destroyed container than there is nothing to do. |
| if (!cgroups::exists(*cgroupsInfo.freezerHierarchy, cgroup)) { |
| LOG(WARNING) << "Couldn't find freezer cgroup for container " |
| << container.id << " so assuming partially destroyed"; |
| |
| return _destroyCgroups(container); |
| } |
| |
| LOG(INFO) << "Destroying cgroup" |
| " '" << path::join(*cgroupsInfo.freezerHierarchy, cgroup) << "'"; |
| |
| // TODO(benh): If this is the last container at a nesting level, |
| // should we also delete the `CGROUP_SEPARATOR` cgroup too? |
| |
| // TODO(benh): What if we fail to destroy the container? Should we |
| // retry? |
| return cgroups::destroy( |
| *cgroupsInfo.freezerHierarchy, |
| cgroup, |
| flags.cgroups_destroy_timeout) |
| .then(defer(self(), &LinuxLauncherProcess::_destroyCgroups, container)); |
| } |
| |
| |
| Future<Nothing> LinuxLauncherProcess::_destroyCgroups( |
| const Container& container) |
| { |
| if (cgroupsInfo.systemdHierarchy.isNone()) { |
| return Nothing(); |
| } |
| |
| const string cgroup = |
| containerizer::paths::getCgroupPath(flags.cgroups_root, container.id); |
| |
| if (!cgroups::exists(*cgroupsInfo.systemdHierarchy, cgroup)) { |
| return Nothing(); |
| } |
| |
| LOG(INFO) << "Destroying cgroup" |
| " '" << path::join(*cgroupsInfo.systemdHierarchy, cgroup) << "'"; |
| |
| return cgroups::destroy( |
| *cgroupsInfo.systemdHierarchy, |
| cgroup, |
| flags.cgroups_destroy_timeout); |
| } |
| |
| |
| Future<Nothing> LinuxLauncherProcess::destroyCgroups2( |
| const Container& container) |
| { |
| const string& cgroup = |
| containerizer::paths::cgroups2::container(flags.cgroups_root, container.id); |
| |
| containers.erase(container.id); |
| |
| LOG(INFO) << "Destroying cgroup '" << cgroup << "'"; |
| |
| return cgroups2::destroy(cgroup); |
| } |
| |
| |
| Future<ContainerStatus> LinuxLauncherProcess::status( |
| const ContainerID& containerId) |
| { |
| Option<Container> container = containers.get(containerId); |
| if (container.isNone()) { |
| return Failure("Container does not exist"); |
| } |
| |
| ContainerStatus status; |
| if (container->pid.isSome()) { |
| status.set_executor_pid(container->pid.get()); |
| } |
| |
| return status; |
| } |
| |
| } // namespace slave { |
| } // namespace internal { |
| } // namespace mesos { |