blob: 732b1c65febdc78d8854e571bb02a9d367528434 [file] [log] [blame]
// 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 <sstream>
#include <process/defer.hpp>
#include <process/id.hpp>
#include <process/pid.hpp>
#include <stout/bytes.hpp>
#include "common/protobuf_utils.hpp"
#include "linux/cgroups2.hpp"
#include "slave/containerizer/mesos/isolators/cgroups2/constants.hpp"
#include "slave/containerizer/mesos/isolators/cgroups2/controllers/memory.hpp"
using process::Failure;
using process::Future;
using process::PID;
using process::Owned;
using cgroups2::memory::Stats;
using mesos::slave::ContainerConfig;
using mesos::slave::ContainerLimitation;
using std::ostringstream;
using std::string;
namespace mesos {
namespace internal {
namespace slave {
Try<Owned<ControllerProcess>> MemoryControllerProcess::create(const Flags& flags)
{
return Owned<ControllerProcess>(new MemoryControllerProcess(flags));
}
MemoryControllerProcess::MemoryControllerProcess(const Flags& _flags)
: ProcessBase(process::ID::generate("cgroups-v2-memory-controller")),
ControllerProcess(_flags) {}
string MemoryControllerProcess::name() const
{
return CGROUPS_V2_CONTROLLER_MEMORY_NAME;
}
Future<Nothing> MemoryControllerProcess::prepare(
const ContainerID& containerId,
const string& cgroup,
const ContainerConfig& containerConfig)
{
if (infos.contains(containerId)) {
return Failure("Already prepared");
}
infos.put(containerId, Info());
return Nothing();
}
Future<Nothing> MemoryControllerProcess::isolate(
const ContainerID& containerId,
const string& cgroup,
pid_t pid)
{
if (!infos.contains(containerId)) {
return Failure("Unknown container");
}
// TODO(dleamy): Implement manual OOM score adjustment, similar to as it done
// in the cgroups v1 isolator.
return Nothing();
}
Future<Nothing> MemoryControllerProcess::recover(
const ContainerID& containerId,
const string& cgroup)
{
if (infos.contains(containerId)) {
return Failure("Already recovered");
}
infos.put(containerId, Info());
infos[containerId].hardLimitUpdated = true;
return Nothing();
}
Future<Nothing> MemoryControllerProcess::update(
const ContainerID& containerId,
const string& cgroup,
const Resources& resourceRequests,
const google::protobuf::Map<string, Value::Scalar>& resourceLimits)
{
if (!infos.contains(containerId)) {
return Failure("Unknown container");
}
if (resourceRequests.mem().isNone()) {
return Failure("No memory resources requested");
}
Bytes memory = *resourceRequests.mem();
Bytes softLimit = std::max(memory, MIN_MEMORY);
// Set the soft memory limit.
Try<Nothing> low = cgroups2::memory::set_low(cgroup, softLimit);
if (low.isError()) {
return Failure("Failed to set soft memory limit: " + low.error());
}
LOG(INFO) << "Updated soft memory limit to " << softLimit << " for container "
<< containerId;
// Determine the new hard memory limit.
Option<Bytes> newHardLimit = [&resourceLimits, &softLimit]() -> Option<Bytes>
{
if (resourceLimits.count("mem") > 0) {
double requestedLimit = resourceLimits.at("mem").value();
if (std::isinf(requestedLimit)) {
return None();
}
return std::max(
Megabytes(static_cast<uint64_t>(requestedLimit)), MIN_MEMORY);
}
return softLimit;
}();
Result<Bytes> currentHardLimit = cgroups2::memory::max(cgroup);
if (currentHardLimit.isError()) {
return Failure("Failed to get current hard memory limit: "
+ currentHardLimit.error());
}
// We only update the hard limit if:
// 1) The hard limit has not yet been set for the container, or
// 2) The new hard limit is greater than the existing hard limit.
//
// This is done to avoid the chance of triggering an OOM by reducing the
// hard limit to below the current memory usage.
bool updateHardLimit = !infos[containerId].hardLimitUpdated
|| newHardLimit.isNone() // infinite memory limit
|| *newHardLimit > *currentHardLimit;
if (updateHardLimit) {
Try<Nothing> max = cgroups2::memory::set_max(cgroup, newHardLimit);
if (max.isError()) {
return Failure("Failed to set hard memory limit: " + max.error());
}
infos[containerId].hardLimitUpdated = true;
}
return Nothing();
}
Future<Nothing> MemoryControllerProcess::cleanup(
const ContainerID& containerId,
const string& cgroup)
{
if (!infos.contains(containerId)) {
LOG(INFO) << "Ignoring memory cleanup for unknown container "
<< containerId;
return Nothing();
}
infos.erase(containerId);
return Nothing();
}
} // namespace slave {
} // namespace internal {
} // namespace mesos {