mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-28 08:35:22 +00:00
ea7d0ca37c
I've encountered the following bug, but only on Gentoo with systemd and CGroupsV2. I've started an LXC container successfully but destroying it reported the following error: error: Failed to destroy domain 'amd64' error: internal error: failed to get cgroup backend for 'pathOfController' Debugging showed, that CGroup hierarchy is full of surprises: /sys/fs/cgroup/machine.slice/machine-lxc\x2d861\x2damd64.scope/ └── libvirt ├── dev-hugepages.mount ├── dev-mqueue.mount ├── init.scope ├── sys-fs-fuse-connections.mount ├── sys-kernel-config.mount ├── sys-kernel-debug.mount ├── sys-kernel-tracing.mount ├── system.slice │ ├── console-getty.service │ ├── dbus.service │ ├── system-getty.slice │ ├── system-modprobe.slice │ ├── systemd-journald.service │ ├── systemd-logind.service │ └── tmp.mount └── user.slice For comparison, here's the same container on recent Rawhide: /sys/fs/cgroup/machine.slice/machine-lxc\x2d13550\x2damd64.scope/ └── libvirt Anyway, those nested directories should not be a problem, because virCgroupKillRecursiveInternal() removes them recursively, right? Sort of. The function really does remove nested directories, but it assumes that every directory has the same controller as the rest. Just take a look at virCgroupV2KillRecursive() - it gets 'Any' controller (the first one it found in ".scope") and then passes it to virCgroupKillRecursiveInternal(). This assumption is not true though. The controllers found in ".scope" are the following: cpuset cpu io memory pids while "libvirt" has fewer: cpuset cpu io memory Up until now it's not problem, because of how we order controllers internally - "cpu" is the first and thus picking "Any" controller returns just that. But the rest of directories has no controllers, their "cgroup.controllers" is just empty. What fixes the bug is dropping @controller argument from virCgroupKillRecursiveInternal() and letting each iteration work pick its own controller. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Pavel Hrdina <phrdina@redhat.com>
1998 lines
54 KiB
C
1998 lines
54 KiB
C
/*
|
|
* vircgroupv2.c: methods for cgroups v2 backend
|
|
*
|
|
* Copyright (C) 2018 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <config.h>
|
|
|
|
#include <unistd.h>
|
|
#ifdef __linux__
|
|
# include <mntent.h>
|
|
# include <sys/mount.h>
|
|
#endif /* __linux__ */
|
|
|
|
#include "internal.h"
|
|
|
|
#define LIBVIRT_VIRCGROUPPRIV_H_ALLOW
|
|
#include "vircgrouppriv.h"
|
|
|
|
#include "viralloc.h"
|
|
#include "virbpf.h"
|
|
#include "vircgroup.h"
|
|
#include "vircgroupbackend.h"
|
|
#include "vircgroupv2.h"
|
|
#include "vircgroupv2devices.h"
|
|
#include "virerror.h"
|
|
#include "virfile.h"
|
|
#include "virlog.h"
|
|
#include "virstring.h"
|
|
#include "virsystemd.h"
|
|
|
|
VIR_LOG_INIT("util.cgroup");
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_CGROUP
|
|
|
|
VIR_ENUM_DECL(virCgroupV2Controller);
|
|
VIR_ENUM_IMPL(virCgroupV2Controller,
|
|
VIR_CGROUP_CONTROLLER_LAST,
|
|
"cpu", "cpuacct", "cpuset", "memory", "devices",
|
|
"freezer", "io", "net_cls", "perf_event", "name=systemd",
|
|
);
|
|
|
|
#ifdef __linux__
|
|
|
|
/* We're looking for one 'cgroup2' fs mount which has some
|
|
* controllers enabled. */
|
|
static bool
|
|
virCgroupV2Available(void)
|
|
{
|
|
bool ret = false;
|
|
FILE *mounts = NULL;
|
|
struct mntent entry;
|
|
char buf[CGROUP_MAX_VAL];
|
|
|
|
if (!(mounts = fopen("/proc/mounts", "r")))
|
|
return false;
|
|
|
|
while (getmntent_r(mounts, &entry, buf, sizeof(buf)) != NULL) {
|
|
g_autofree char *contFile = NULL;
|
|
g_autofree char *contStr = NULL;
|
|
|
|
if (STRNEQ(entry.mnt_type, "cgroup2"))
|
|
continue;
|
|
|
|
/* Systemd uses cgroup v2 for process tracking but no controller is
|
|
* available. We should consider this configuration as cgroup v2 is
|
|
* not available. */
|
|
contFile = g_strdup_printf("%s/cgroup.controllers", entry.mnt_dir);
|
|
|
|
if (virFileReadAll(contFile, 1024 * 1024, &contStr) < 0)
|
|
goto cleanup;
|
|
|
|
if (STREQ(contStr, ""))
|
|
continue;
|
|
|
|
ret = true;
|
|
break;
|
|
}
|
|
|
|
cleanup:
|
|
VIR_FORCE_FCLOSE(mounts);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static bool
|
|
virCgroupV2ValidateMachineGroup(virCgroup *group,
|
|
const char *name G_GNUC_UNUSED,
|
|
const char *drivername,
|
|
const char *machinename)
|
|
{
|
|
g_autofree char *partmachinename = NULL;
|
|
g_autofree char *scopename = NULL;
|
|
char *tmp;
|
|
|
|
partmachinename = g_strdup_printf("%s.libvirt-%s", machinename, drivername);
|
|
|
|
if (virCgroupPartitionEscape(&partmachinename) < 0)
|
|
return false;
|
|
|
|
if (!(scopename = virSystemdMakeScopeName(machinename, drivername,
|
|
false))) {
|
|
return false;
|
|
}
|
|
|
|
if (virCgroupPartitionEscape(&scopename) < 0)
|
|
return false;
|
|
|
|
if (!(tmp = strrchr(group->unified.placement, '/')))
|
|
return false;
|
|
|
|
tmp++;
|
|
|
|
if (STRNEQ(tmp, partmachinename) &&
|
|
STRNEQ(tmp, scopename)) {
|
|
VIR_DEBUG("Name '%s' for unified does not match '%s' or '%s'",
|
|
tmp, partmachinename, scopename);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2CopyMounts(virCgroup *group,
|
|
virCgroup *parent)
|
|
{
|
|
group->unified.mountPoint = g_strdup(parent->unified.mountPoint);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2CopyPlacement(virCgroup *group,
|
|
const char *path,
|
|
virCgroup *parent)
|
|
{
|
|
bool delim = STREQ(parent->unified.placement, "/") || STREQ(path, "");
|
|
|
|
VIR_DEBUG("group=%p path=%s parent=%p", group, path, parent);
|
|
|
|
/*
|
|
* parent == "/" + path="" => "/"
|
|
* parent == "/libvirt.service" + path == "" => "/libvirt.service"
|
|
* parent == "/libvirt.service" + path == "foo" => "/libvirt.service/foo"
|
|
*/
|
|
group->unified.placement = g_strdup_printf("%s%s%s",
|
|
parent->unified.placement,
|
|
delim ? "" : "/",
|
|
path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2DetectMounts(virCgroup *group,
|
|
const char *mntType,
|
|
const char *mntOpts G_GNUC_UNUSED,
|
|
const char *mntDir)
|
|
{
|
|
if (STRNEQ(mntType, "cgroup2"))
|
|
return 0;
|
|
|
|
VIR_FREE(group->unified.mountPoint);
|
|
|
|
group->unified.mountPoint = g_strdup(mntDir);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2DetectPlacement(virCgroup *group,
|
|
const char *path,
|
|
const char *controllers,
|
|
const char *selfpath)
|
|
{
|
|
g_autofree char *placement = g_strdup(selfpath);
|
|
char *tmp = NULL;
|
|
|
|
if (group->unified.placement)
|
|
return 0;
|
|
|
|
VIR_DEBUG("group=%p path=%s controllers=%s selfpath=%s",
|
|
group, path, controllers, selfpath);
|
|
|
|
/* controllers == "" indicates the cgroupv2 controller path */
|
|
if (STRNEQ(controllers, ""))
|
|
return 0;
|
|
|
|
/* Running VM will have the main thread placed in emulator cgroup
|
|
* but we need to get the main cgroup. */
|
|
tmp = g_strrstr(placement, "/emulator");
|
|
if (tmp)
|
|
*tmp = '\0';
|
|
|
|
/* On systemd we create a nested cgroup for some cgroup tasks
|
|
* but the placement should point to the root cgroup. */
|
|
tmp = g_strrstr(placement, "/libvirt");
|
|
if (tmp)
|
|
*tmp = '\0';
|
|
|
|
/*
|
|
* selfpath == "/" + path="" -> "/"
|
|
* selfpath == "/libvirt.service" + path == "" -> "/libvirt.service"
|
|
* selfpath == "/libvirt.service" + path == "foo" -> "/libvirt.service/foo"
|
|
*/
|
|
group->unified.placement = g_strdup_printf("%s%s%s", placement,
|
|
(STREQ(selfpath, "/") || STREQ(path, "") ? "" : "/"), path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetPlacement(virCgroup *group,
|
|
const char *path)
|
|
{
|
|
group->unified.placement = g_strdup(path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2ValidatePlacement(virCgroup *group,
|
|
pid_t pid G_GNUC_UNUSED)
|
|
{
|
|
if (!group->unified.placement) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Could not find placement for v2 controller"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static char *
|
|
virCgroupV2StealPlacement(virCgroup *group)
|
|
{
|
|
return g_steal_pointer(&group->unified.placement);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2ParseControllersFile(virCgroup *group,
|
|
virCgroup *parent)
|
|
{
|
|
int rc;
|
|
g_autofree char *contStr = NULL;
|
|
g_autofree char *contFile = NULL;
|
|
char **contList = NULL;
|
|
char **tmp;
|
|
|
|
if (parent) {
|
|
contFile = g_strdup_printf("%s%s/cgroup.controllers",
|
|
parent->unified.mountPoint,
|
|
NULLSTR_EMPTY(parent->unified.placement));
|
|
} else {
|
|
contFile = g_strdup_printf("%s%s/cgroup.controllers",
|
|
group->unified.mountPoint,
|
|
NULLSTR_EMPTY(group->unified.placement));
|
|
}
|
|
|
|
rc = virFileReadAll(contFile, 1024 * 1024, &contStr);
|
|
if (rc < 0) {
|
|
virReportSystemError(errno, _("Unable to read from '%s'"), contFile);
|
|
return -1;
|
|
}
|
|
|
|
virTrimSpaces(contStr, NULL);
|
|
|
|
contList = g_strsplit(contStr, " ", 20);
|
|
if (!contList)
|
|
return -1;
|
|
|
|
tmp = contList;
|
|
|
|
while (*tmp) {
|
|
int type = virCgroupV2ControllerTypeFromString(*tmp);
|
|
|
|
if (type >= 0)
|
|
group->unified.controllers |= 1 << type;
|
|
|
|
tmp++;
|
|
}
|
|
|
|
g_strfreev(contList);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2DetectControllers(virCgroup *group,
|
|
int controllers,
|
|
virCgroup *parent,
|
|
int detected)
|
|
{
|
|
size_t i;
|
|
|
|
if (virCgroupV2ParseControllersFile(group, parent) < 0)
|
|
return -1;
|
|
|
|
/* In cgroup v2 there is no cpuacct controller, the cpu.stat file always
|
|
* exists with usage stats. */
|
|
group->unified.controllers |= 1 << VIR_CGROUP_CONTROLLER_CPUACCT;
|
|
|
|
if (virCgroupV2DevicesAvailable(group))
|
|
group->unified.controllers |= 1 << VIR_CGROUP_CONTROLLER_DEVICES;
|
|
|
|
if (controllers >= 0)
|
|
group->unified.controllers &= controllers;
|
|
|
|
group->unified.controllers &= ~detected;
|
|
|
|
for (i = 0; i < VIR_CGROUP_CONTROLLER_LAST; i++)
|
|
VIR_DEBUG("Controller '%s' present=%s",
|
|
virCgroupV2ControllerTypeToString(i),
|
|
(group->unified.controllers & 1 << i) ? "yes" : "no");
|
|
|
|
return group->unified.controllers;
|
|
}
|
|
|
|
|
|
static bool
|
|
virCgroupV2HasController(virCgroup *group,
|
|
int controller)
|
|
{
|
|
return group->unified.controllers & (1 << controller);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetAnyController(virCgroup *group)
|
|
{
|
|
/* The least significant bit is position 1. */
|
|
return __builtin_ffs(group->unified.controllers) - 1;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2PathOfController(virCgroup *group,
|
|
int controller,
|
|
const char *key,
|
|
char **path)
|
|
{
|
|
if (!virCgroupV2HasController(group, controller)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("v2 controller '%s' is not available"),
|
|
virCgroupV2ControllerTypeToString(controller));
|
|
return -1;
|
|
}
|
|
|
|
*path = g_strdup_printf("%s%s/%s", group->unified.mountPoint,
|
|
group->unified.placement, NULLSTR_EMPTY(key));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virCgroupV2EnableController:
|
|
*
|
|
* Returns: -1 on fatal error
|
|
* -2 if we failed to write into cgroup.subtree_control
|
|
* 0 on success
|
|
*/
|
|
static int
|
|
virCgroupV2EnableController(virCgroup *group,
|
|
virCgroup *parent,
|
|
int controller,
|
|
bool report)
|
|
{
|
|
g_autofree char *val = NULL;
|
|
g_autofree char *path = NULL;
|
|
|
|
val = g_strdup_printf("+%s", virCgroupV2ControllerTypeToString(controller));
|
|
|
|
if (virCgroupPathOfController(parent, controller,
|
|
"cgroup.subtree_control", &path) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (virFileWriteStr(path, val, 0) < 0) {
|
|
if (report) {
|
|
virReportSystemError(errno,
|
|
_("Failed to enable controller '%s' for '%s'"),
|
|
val, path);
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
group->unified.controllers |= 1 << controller;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2AddTask(virCgroup *group,
|
|
pid_t pid,
|
|
unsigned int flags);
|
|
|
|
|
|
static int
|
|
virCgroupV2MakeGroup(virCgroup *parent,
|
|
virCgroup *group,
|
|
bool create,
|
|
pid_t pid,
|
|
unsigned int flags)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
int controller;
|
|
|
|
if (flags & VIR_CGROUP_SYSTEMD) {
|
|
VIR_DEBUG("Running with systemd so we should not create cgroups ourselves.");
|
|
return 0;
|
|
}
|
|
|
|
controller = virCgroupV2GetAnyController(group);
|
|
if (virCgroupV2PathOfController(group, controller, "", &path) < 0)
|
|
return -1;
|
|
|
|
VIR_DEBUG("Make controller %s", path);
|
|
|
|
if (!virFileExists(path) &&
|
|
(!create || (mkdir(path, 0755) < 0 && errno != EEXIST))) {
|
|
virReportSystemError(errno, _("Failed to create v2 cgroup '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (create) {
|
|
if (flags & VIR_CGROUP_THREAD) {
|
|
if (virCgroupSetValueStr(group, controller,
|
|
"cgroup.type", "threaded") < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (virCgroupV2HasController(parent, VIR_CGROUP_CONTROLLER_CPU) &&
|
|
virCgroupV2EnableController(group, parent,
|
|
VIR_CGROUP_CONTROLLER_CPU,
|
|
true) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (virCgroupV2HasController(parent, VIR_CGROUP_CONTROLLER_CPUSET) &&
|
|
virCgroupV2EnableController(group, parent,
|
|
VIR_CGROUP_CONTROLLER_CPUSET,
|
|
true) < 0) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
size_t i;
|
|
|
|
if (pid > 0) {
|
|
if (virCgroupV2AddTask(group, pid, VIR_CGROUP_TASK_PROCESS) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < VIR_CGROUP_CONTROLLER_LAST; i++) {
|
|
int rc;
|
|
|
|
if (!virCgroupV2HasController(parent, i))
|
|
continue;
|
|
|
|
/* Controllers that are implicitly enabled if available. */
|
|
if (i == VIR_CGROUP_CONTROLLER_CPUACCT ||
|
|
i == VIR_CGROUP_CONTROLLER_DEVICES) {
|
|
continue;
|
|
}
|
|
|
|
rc = virCgroupV2EnableController(group, parent, i, false);
|
|
if (rc < 0) {
|
|
if (rc == -2) {
|
|
virResetLastError();
|
|
VIR_DEBUG("failed to enable '%s' controller, skipping",
|
|
virCgroupV2ControllerTypeToString(i));
|
|
group->unified.controllers &= ~(1 << i);
|
|
continue;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool
|
|
virCgroupV2Exists(virCgroup *group)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
int controller;
|
|
|
|
controller = virCgroupV2GetAnyController(group);
|
|
if (virCgroupV2PathOfController(group, controller, "", &path) < 0)
|
|
return false;
|
|
|
|
return virFileExists(path);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2Remove(virCgroup *group)
|
|
{
|
|
g_autofree char *grppath = NULL;
|
|
virCgroup *parent = virCgroupGetNested(group);
|
|
int controller;
|
|
|
|
/* Don't delete the root group, if we accidentally
|
|
ended up in it for some reason */
|
|
if (STREQ(group->unified.placement, "/"))
|
|
return 0;
|
|
|
|
controller = virCgroupV2GetAnyController(group);
|
|
if (virCgroupV2PathOfController(group, controller, "", &grppath) < 0)
|
|
return 0;
|
|
|
|
if (virCgroupV2DevicesCloseProg(parent) < 0)
|
|
return -1;
|
|
|
|
return virCgroupRemoveRecursively(grppath);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2AddTask(virCgroup *group,
|
|
pid_t pid,
|
|
unsigned int flags)
|
|
{
|
|
int controller = virCgroupV2GetAnyController(group);
|
|
|
|
if (flags & VIR_CGROUP_TASK_THREAD)
|
|
return virCgroupSetValueI64(group, controller, "cgroup.threads", pid);
|
|
else
|
|
return virCgroupSetValueI64(group, controller, "cgroup.procs", pid);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2HasEmptyTasks(virCgroup *cgroup,
|
|
int controller)
|
|
{
|
|
int ret = -1;
|
|
g_autofree char *content = NULL;
|
|
|
|
ret = virCgroupGetValueStr(cgroup, controller, "cgroup.threads", &content);
|
|
|
|
if (ret == 0 && content[0] == '\0')
|
|
ret = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2KillRecursive(virCgroup *group,
|
|
int signum,
|
|
GHashTable *pids)
|
|
{
|
|
return virCgroupKillRecursiveInternal(group, signum, pids,
|
|
"cgroup.threads", false);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2BindMount(virCgroup *group,
|
|
const char *oldroot,
|
|
const char *mountopts G_GNUC_UNUSED)
|
|
{
|
|
g_autofree char *src = NULL;
|
|
|
|
VIR_DEBUG("Mounting cgroups at '%s'", group->unified.mountPoint);
|
|
|
|
if (g_mkdir_with_parents(group->unified.mountPoint, 0777) < 0) {
|
|
virReportSystemError(errno, _("Unable to create directory %s"),
|
|
group->unified.mountPoint);
|
|
return -1;
|
|
}
|
|
|
|
src = g_strdup_printf("%s%s", oldroot, group->unified.mountPoint);
|
|
|
|
if (mount(src, group->unified.mountPoint, "none", MS_BIND, NULL) < 0) {
|
|
virReportSystemError(errno, _("Failed to bind cgroup '%s' on '%s'"),
|
|
src, group->unified.mountPoint);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetOwner(virCgroup *cgroup,
|
|
uid_t uid,
|
|
gid_t gid,
|
|
int controllers G_GNUC_UNUSED)
|
|
{
|
|
g_autofree char *base = NULL;
|
|
|
|
base = g_strdup_printf("%s%s", cgroup->unified.mountPoint,
|
|
cgroup->unified.placement);
|
|
|
|
if (virFileChownFiles(base, uid, gid) < 0)
|
|
return -1;
|
|
|
|
if (chown(base, uid, gid) < 0) {
|
|
virReportSystemError(errno, _("cannot chown '%s' to (%u, %u)"),
|
|
base, uid, gid);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetBlkioWeight(virCgroup *group,
|
|
unsigned int weight)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
const char *format = "%u";
|
|
|
|
if (virCgroupV2PathOfController(group, VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.bfq.weight", &path) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!virFileExists(path)) {
|
|
VIR_FREE(path);
|
|
format = "default %u";
|
|
|
|
if (virCgroupV2PathOfController(group, VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.weight", &path) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (!virFileExists(path)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("blkio weight is valid only for bfq or cfq scheduler"));
|
|
return -1;
|
|
}
|
|
|
|
if (group->unitName) {
|
|
GVariant *value = g_variant_new("t", weight);
|
|
|
|
return virCgroupSetValueDBus(group->unitName, "IOWeight", value);
|
|
} else {
|
|
g_autofree char *value = g_strdup_printf(format, weight);
|
|
|
|
return virCgroupSetValueRaw(path, value);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetBlkioWeight(virCgroup *group,
|
|
unsigned int *weight)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
g_autofree char *value = NULL;
|
|
char *tmp;
|
|
|
|
if (virCgroupV2PathOfController(group, VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.bfq.weight", &path) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!virFileExists(path)) {
|
|
VIR_FREE(path);
|
|
|
|
if (virCgroupV2PathOfController(group, VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.weight", &path) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (!virFileExists(path)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("blkio weight is valid only for bfq or cfq scheduler"));
|
|
return -1;
|
|
}
|
|
|
|
if (virCgroupGetValueRaw(path, &value) < 0)
|
|
return -1;
|
|
|
|
if ((tmp = strstr(value, "default "))) {
|
|
tmp += strlen("default ");
|
|
} else {
|
|
tmp = value;
|
|
}
|
|
|
|
if (virStrToLong_ui(tmp, &tmp, 10, weight) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse '%s' as an integer"),
|
|
tmp);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetBlkioIoServiced(virCgroup *group,
|
|
long long *bytes_read,
|
|
long long *bytes_write,
|
|
long long *requests_read,
|
|
long long *requests_write)
|
|
{
|
|
long long stats_val;
|
|
g_autofree char *str1 = NULL;
|
|
char *p1;
|
|
size_t i;
|
|
|
|
const char *value_names[] = {
|
|
"rbytes=",
|
|
"wbytes=",
|
|
"rios=",
|
|
"wios=",
|
|
};
|
|
long long *value_ptrs[] = {
|
|
bytes_read,
|
|
bytes_write,
|
|
requests_read,
|
|
requests_write
|
|
};
|
|
|
|
*bytes_read = 0;
|
|
*bytes_write = 0;
|
|
*requests_read = 0;
|
|
*requests_write = 0;
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.stat", &str1) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* sum up all entries of the same kind, from all devices */
|
|
for (i = 0; i < G_N_ELEMENTS(value_names); i++) {
|
|
p1 = str1;
|
|
|
|
while ((p1 = strstr(p1, value_names[i]))) {
|
|
p1 += strlen(value_names[i]);
|
|
if (virStrToLong_ll(p1, &p1, 10, &stats_val) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Cannot parse byte '%s' stat '%s'"),
|
|
value_names[i], p1);
|
|
return -1;
|
|
}
|
|
|
|
if (stats_val < 0 ||
|
|
(stats_val > 0 && *value_ptrs[i] > (LLONG_MAX - stats_val))) {
|
|
virReportError(VIR_ERR_OVERFLOW,
|
|
_("Sum of byte '%s' stat overflows"),
|
|
value_names[i]);
|
|
return -1;
|
|
}
|
|
*value_ptrs[i] += stats_val;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetBlkioIoDeviceServiced(virCgroup *group,
|
|
const char *path,
|
|
long long *bytes_read,
|
|
long long *bytes_write,
|
|
long long *requests_read,
|
|
long long *requests_write)
|
|
{
|
|
g_autofree char *str1 = NULL;
|
|
g_autofree char *str2 = NULL;
|
|
char *p1;
|
|
size_t i;
|
|
|
|
const char *value_names[] = {
|
|
"rbytes=",
|
|
"wbytes=",
|
|
"rios=",
|
|
"wios=",
|
|
};
|
|
long long *value_ptrs[] = {
|
|
bytes_read,
|
|
bytes_write,
|
|
requests_read,
|
|
requests_write
|
|
};
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.stat", &str1) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!(str2 = virCgroupGetBlockDevString(path)))
|
|
return -1;
|
|
|
|
if (!(p1 = strstr(str1, str2))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Cannot find byte stats for block device '%s'"),
|
|
str2);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(value_names); i++) {
|
|
if (!(p1 = strstr(p1, value_names[i]))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Cannot find byte '%s' stats for block device '%s'"),
|
|
value_names[i], str2);
|
|
return -1;
|
|
}
|
|
|
|
p1 += strlen(value_names[i]);
|
|
if (virStrToLong_ll(p1, &p1, 10, value_ptrs[i]) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Cannot parse '%s' stat '%s'"),
|
|
value_names[i], p1);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetBlkioDeviceWeight(virCgroup *group,
|
|
const char *devPath,
|
|
unsigned int weight)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
|
|
if (virCgroupV2PathOfController(group, VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.weight", &path) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!virFileExists(path)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("blkio device weight is valid only for cfq scheduler"));
|
|
return -1;
|
|
}
|
|
|
|
if (group->unitName) {
|
|
GVariant *value = NULL;
|
|
|
|
value = g_variant_new_parsed("[(%s, uint64 %u)]", path, weight);
|
|
|
|
return virCgroupSetValueDBus(group->unitName, "IODeviceWeight", value);
|
|
} else {
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *blkstr = NULL;
|
|
|
|
if (!(blkstr = virCgroupGetBlockDevString(devPath)))
|
|
return -1;
|
|
|
|
str = g_strdup_printf("%s%d", blkstr, weight);
|
|
|
|
return virCgroupSetValueRaw(path, str);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetBlkioDeviceWeight(virCgroup *group,
|
|
const char *devPath,
|
|
unsigned int *weight)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *value = NULL;
|
|
char *tmp;
|
|
|
|
if (virCgroupV2PathOfController(group, VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.weight", &path) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!virFileExists(path)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("blkio device weight is valid only for cfq scheduler"));
|
|
return -1;
|
|
}
|
|
|
|
if (virCgroupGetValueRaw(path, &value) < 0)
|
|
return -1;
|
|
|
|
if (virCgroupGetValueForBlkDev(value, devPath, &str) < 0)
|
|
return -1;
|
|
|
|
if (!str) {
|
|
*weight = 0;
|
|
} else if (virStrToLong_ui(str, &tmp, 10, weight) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse '%s' as an integer"),
|
|
str);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetBlkioDeviceReadIops(virCgroup *group,
|
|
const char *path,
|
|
unsigned int riops)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *blkstr = NULL;
|
|
|
|
if (!(blkstr = virCgroupGetBlockDevString(path)))
|
|
return -1;
|
|
|
|
if (riops == 0) {
|
|
str = g_strdup_printf("%sriops=max", blkstr);
|
|
} else {
|
|
str = g_strdup_printf("%sriops=%u", blkstr, riops);
|
|
}
|
|
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.max",
|
|
str);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetBlkioDeviceReadIops(virCgroup *group,
|
|
const char *path,
|
|
unsigned int *riops)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *value = NULL;
|
|
const char *name = "riops=";
|
|
char *tmp;
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.max",
|
|
&value) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (virCgroupGetValueForBlkDev(value, path, &str) < 0)
|
|
return -1;
|
|
|
|
if (!str) {
|
|
*riops = 0;
|
|
} else {
|
|
if (!(tmp = strstr(str, name))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to find '%s' limit for block device '%s'"),
|
|
name, path);
|
|
return -1;
|
|
}
|
|
tmp += strlen(name);
|
|
|
|
if (STRPREFIX(tmp, "max")) {
|
|
*riops = 0;
|
|
} else if (virStrToLong_ui(tmp, &tmp, 10, riops) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse '%s' as an integer"),
|
|
str);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetBlkioDeviceWriteIops(virCgroup *group,
|
|
const char *path,
|
|
unsigned int wiops)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *blkstr = NULL;
|
|
|
|
if (!(blkstr = virCgroupGetBlockDevString(path)))
|
|
return -1;
|
|
|
|
if (wiops == 0) {
|
|
str = g_strdup_printf("%swiops=max", blkstr);
|
|
} else {
|
|
str = g_strdup_printf("%swiops=%u", blkstr, wiops);
|
|
}
|
|
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.max",
|
|
str);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetBlkioDeviceWriteIops(virCgroup *group,
|
|
const char *path,
|
|
unsigned int *wiops)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *value = NULL;
|
|
const char *name = "wiops=";
|
|
char *tmp;
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.max",
|
|
&value) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (virCgroupGetValueForBlkDev(value, path, &str) < 0)
|
|
return -1;
|
|
|
|
if (!str) {
|
|
*wiops = 0;
|
|
} else {
|
|
if (!(tmp = strstr(str, name))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to find '%s' limit for block device '%s'"),
|
|
name, path);
|
|
return -1;
|
|
}
|
|
tmp += strlen(name);
|
|
|
|
if (STRPREFIX(tmp, "max")) {
|
|
*wiops = 0;
|
|
} else if (virStrToLong_ui(tmp, &tmp, 10, wiops) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse '%s' as an integer"),
|
|
str);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetBlkioDeviceReadBps(virCgroup *group,
|
|
const char *path,
|
|
unsigned long long rbps)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *blkstr = NULL;
|
|
|
|
if (!(blkstr = virCgroupGetBlockDevString(path)))
|
|
return -1;
|
|
|
|
if (rbps == 0) {
|
|
str = g_strdup_printf("%srbps=max", blkstr);
|
|
} else {
|
|
str = g_strdup_printf("%srbps=%llu", blkstr, rbps);
|
|
}
|
|
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.max",
|
|
str);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetBlkioDeviceReadBps(virCgroup *group,
|
|
const char *path,
|
|
unsigned long long *rbps)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *value = NULL;
|
|
const char *name = "rbps=";
|
|
char *tmp;
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.max",
|
|
&value) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (virCgroupGetValueForBlkDev(value, path, &str) < 0)
|
|
return -1;
|
|
|
|
if (!str) {
|
|
*rbps = 0;
|
|
} else {
|
|
if (!(tmp = strstr(str, name))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to find '%s' limit for block device '%s'"),
|
|
name, path);
|
|
return -1;
|
|
}
|
|
tmp += strlen(name);
|
|
|
|
if (STRPREFIX(tmp, "max")) {
|
|
*rbps = 0;
|
|
} else if (virStrToLong_ull(tmp, &tmp, 10, rbps) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse '%s' as an integer"),
|
|
str);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetBlkioDeviceWriteBps(virCgroup *group,
|
|
const char *path,
|
|
unsigned long long wbps)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *blkstr = NULL;
|
|
|
|
if (!(blkstr = virCgroupGetBlockDevString(path)))
|
|
return -1;
|
|
|
|
if (wbps == 0) {
|
|
str = g_strdup_printf("%swbps=max", blkstr);
|
|
} else {
|
|
str = g_strdup_printf("%swbps=%llu", blkstr, wbps);
|
|
}
|
|
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.max",
|
|
str);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetBlkioDeviceWriteBps(virCgroup *group,
|
|
const char *path,
|
|
unsigned long long *wbps)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
g_autofree char *value = NULL;
|
|
const char *name = "wbps=";
|
|
char *tmp;
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_BLKIO,
|
|
"io.max",
|
|
&value) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (virCgroupGetValueForBlkDev(value, path, &str) < 0)
|
|
return -1;
|
|
|
|
if (!str) {
|
|
*wbps = 0;
|
|
} else {
|
|
if (!(tmp = strstr(str, name))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to find '%s' limit for block device '%s'"),
|
|
name, path);
|
|
return -1;
|
|
}
|
|
tmp += strlen(name);
|
|
|
|
if (STRPREFIX(tmp, "max")) {
|
|
*wbps = 0;
|
|
} else if (virStrToLong_ull(tmp, &tmp, 10, wbps) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse '%s' as an integer"),
|
|
str);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetMemory(virCgroup *group,
|
|
unsigned long long kb)
|
|
{
|
|
unsigned long long maxkb = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
|
|
if (kb > maxkb) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("Memory '%llu' must be less than %llu"),
|
|
kb, maxkb);
|
|
return -1;
|
|
}
|
|
|
|
if (kb == maxkb) {
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.max",
|
|
"max");
|
|
} else {
|
|
return virCgroupSetValueU64(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.max",
|
|
kb << 10);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetMemoryStat(virCgroup *group,
|
|
unsigned long long *cache,
|
|
unsigned long long *activeAnon,
|
|
unsigned long long *inactiveAnon,
|
|
unsigned long long *activeFile,
|
|
unsigned long long *inactiveFile,
|
|
unsigned long long *unevictable)
|
|
{
|
|
g_autofree char *stat = NULL;
|
|
char *line = NULL;
|
|
unsigned long long cacheVal = 0;
|
|
unsigned long long activeAnonVal = 0;
|
|
unsigned long long inactiveAnonVal = 0;
|
|
unsigned long long activeFileVal = 0;
|
|
unsigned long long inactiveFileVal = 0;
|
|
unsigned long long unevictableVal = 0;
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.stat",
|
|
&stat) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
line = stat;
|
|
|
|
while (*line) {
|
|
char *newLine = strchr(line, '\n');
|
|
char *valueStr = strchr(line, ' ');
|
|
unsigned long long value;
|
|
|
|
if (newLine)
|
|
*newLine = '\0';
|
|
|
|
if (!valueStr) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Cannot parse 'memory.stat' cgroup file."));
|
|
return -1;
|
|
}
|
|
*valueStr = '\0';
|
|
|
|
if (virStrToLong_ull(valueStr + 1, NULL, 10, &value) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse '%s' as an integer"),
|
|
valueStr + 1);
|
|
return -1;
|
|
}
|
|
|
|
if (STREQ(line, "file"))
|
|
cacheVal = value >> 10;
|
|
else if (STREQ(line, "active_anon"))
|
|
activeAnonVal = value >> 10;
|
|
else if (STREQ(line, "inactive_anon"))
|
|
inactiveAnonVal = value >> 10;
|
|
else if (STREQ(line, "active_file"))
|
|
activeFileVal = value >> 10;
|
|
else if (STREQ(line, "inactive_file"))
|
|
inactiveFileVal = value >> 10;
|
|
else if (STREQ(line, "unevictable"))
|
|
unevictableVal = value >> 10;
|
|
|
|
if (newLine)
|
|
line = newLine + 1;
|
|
else
|
|
break;
|
|
}
|
|
|
|
*cache = cacheVal;
|
|
*activeAnon = activeAnonVal;
|
|
*inactiveAnon = inactiveAnonVal;
|
|
*activeFile = activeFileVal;
|
|
*inactiveFile = inactiveFileVal;
|
|
*unevictable = unevictableVal;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetMemoryUsage(virCgroup *group,
|
|
unsigned long *kb)
|
|
{
|
|
unsigned long long usage_in_bytes;
|
|
int ret = virCgroupGetValueU64(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.current", &usage_in_bytes);
|
|
if (ret == 0)
|
|
*kb = (unsigned long) usage_in_bytes >> 10;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetMemoryHardLimit(virCgroup *group,
|
|
unsigned long long kb)
|
|
{
|
|
return virCgroupV2SetMemory(group, kb);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetMemoryHardLimit(virCgroup *group,
|
|
unsigned long long *kb)
|
|
{
|
|
g_autofree char *value = NULL;
|
|
unsigned long long max;
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.max", &value) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (STREQ(value, "max")) {
|
|
*kb = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
return 0;
|
|
}
|
|
|
|
if (virStrToLong_ull(value, NULL, 10, &max) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to parse value '%s' as number."),
|
|
value);
|
|
return -1;
|
|
}
|
|
|
|
*kb = max >> 10;
|
|
if (*kb >= VIR_DOMAIN_MEMORY_PARAM_UNLIMITED)
|
|
*kb = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetMemorySoftLimit(virCgroup *group,
|
|
unsigned long long kb)
|
|
{
|
|
unsigned long long maxkb = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
|
|
if (kb > maxkb) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("Memory '%llu' must be less than %llu"),
|
|
kb, maxkb);
|
|
return -1;
|
|
}
|
|
|
|
if (kb == maxkb) {
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.high",
|
|
"max");
|
|
} else {
|
|
return virCgroupSetValueU64(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.high",
|
|
kb << 10);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetMemorySoftLimit(virCgroup *group,
|
|
unsigned long long *kb)
|
|
{
|
|
g_autofree char *value = NULL;
|
|
unsigned long long high;
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.high", &value) < 0)
|
|
return -1;
|
|
|
|
if (STREQ(value, "max")) {
|
|
*kb = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
return 0;
|
|
}
|
|
|
|
if (virStrToLong_ull(value, NULL, 10, &high) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to parse value '%s' as number."),
|
|
value);
|
|
return -1;
|
|
}
|
|
|
|
*kb = high >> 10;
|
|
if (*kb >= VIR_DOMAIN_MEMORY_PARAM_UNLIMITED)
|
|
*kb = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetMemSwapHardLimit(virCgroup *group,
|
|
unsigned long long kb)
|
|
{
|
|
unsigned long long maxkb = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
|
|
if (kb > maxkb) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("Memory '%llu' must be less than %llu"),
|
|
kb, maxkb);
|
|
return -1;
|
|
}
|
|
|
|
if (kb == maxkb) {
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.swap.max",
|
|
"max");
|
|
} else {
|
|
return virCgroupSetValueU64(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.swap.max",
|
|
kb << 10);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetMemSwapHardLimit(virCgroup *group,
|
|
unsigned long long *kb)
|
|
{
|
|
g_autofree char *value = NULL;
|
|
unsigned long long max;
|
|
|
|
if (virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.swap.max", &value) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (STREQ(value, "max")) {
|
|
*kb = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
return 0;
|
|
}
|
|
|
|
if (virStrToLong_ull(value, NULL, 10, &max) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to parse value '%s' as number."),
|
|
value);
|
|
return -1;
|
|
}
|
|
|
|
*kb = max >> 10;
|
|
if (*kb >= VIR_DOMAIN_MEMORY_PARAM_UNLIMITED)
|
|
*kb = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetMemSwapUsage(virCgroup *group,
|
|
unsigned long long *kb)
|
|
{
|
|
unsigned long long usage_in_bytes;
|
|
int ret;
|
|
ret = virCgroupGetValueU64(group,
|
|
VIR_CGROUP_CONTROLLER_MEMORY,
|
|
"memory.swap.current", &usage_in_bytes);
|
|
if (ret == 0)
|
|
*kb = (unsigned long) usage_in_bytes >> 10;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetCpuShares(virCgroup *group,
|
|
unsigned long long shares)
|
|
{
|
|
if (shares < VIR_CGROUP_CPU_SHARES_MIN ||
|
|
shares > VIR_CGROUP_CPU_SHARES_MAX) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("shares '%llu' must be in range [%llu, %llu]"),
|
|
shares,
|
|
VIR_CGROUP_CPU_SHARES_MIN,
|
|
VIR_CGROUP_CPU_SHARES_MAX);
|
|
return -1;
|
|
}
|
|
|
|
if (group->unitName) {
|
|
GVariant *value = g_variant_new("t", shares);
|
|
|
|
return virCgroupSetValueDBus(group->unitName, "CPUWeight", value);
|
|
} else {
|
|
return virCgroupSetValueU64(group,
|
|
VIR_CGROUP_CONTROLLER_CPU,
|
|
"cpu.weight", shares);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetCpuShares(virCgroup *group,
|
|
unsigned long long *shares)
|
|
{
|
|
return virCgroupGetValueU64(group,
|
|
VIR_CGROUP_CONTROLLER_CPU,
|
|
"cpu.weight", shares);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetCpuCfsPeriod(virCgroup *group,
|
|
unsigned long long cfs_period)
|
|
{
|
|
g_autofree char *value = NULL;
|
|
g_autofree char *str = NULL;
|
|
char *tmp;
|
|
|
|
if (cfs_period < VIR_CGROUP_CPU_PERIOD_MIN ||
|
|
cfs_period > VIR_CGROUP_CPU_PERIOD_MAX) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("cfs_period '%llu' must be in range (%llu, %llu)"),
|
|
VIR_CGROUP_CPU_PERIOD_MIN,
|
|
VIR_CGROUP_CPU_PERIOD_MAX,
|
|
cfs_period);
|
|
return -1;
|
|
}
|
|
|
|
if (virCgroupGetValueStr(group, VIR_CGROUP_CONTROLLER_CPU,
|
|
"cpu.max", &str) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!(tmp = strchr(str, ' '))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Invalid 'cpu.max' data."));
|
|
return -1;
|
|
}
|
|
*tmp = '\0';
|
|
|
|
value = g_strdup_printf("%s %llu", str, cfs_period);
|
|
|
|
return virCgroupSetValueStr(group, VIR_CGROUP_CONTROLLER_CPU,
|
|
"cpu.max", value);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetCpuCfsPeriod(virCgroup *group,
|
|
unsigned long long *cfs_period)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
char *tmp;
|
|
|
|
if (virCgroupGetValueStr(group, VIR_CGROUP_CONTROLLER_CPU,
|
|
"cpu.max", &str) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!(tmp = strchr(str, ' '))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Invalid 'cpu.max' data."));
|
|
return -1;
|
|
}
|
|
|
|
if (virStrToLong_ull(tmp, &tmp, 10, cfs_period) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to parse value '%s' from cpu.max."), str);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetCpuCfsQuota(virCgroup *group,
|
|
long long cfs_quota)
|
|
{
|
|
if (cfs_quota >= 0 &&
|
|
(cfs_quota < VIR_CGROUP_CPU_QUOTA_MIN ||
|
|
cfs_quota > VIR_CGROUP_CPU_QUOTA_MAX)) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("cfs_quota '%lld' must be in range (%llu, %llu)"),
|
|
cfs_quota,
|
|
VIR_CGROUP_CPU_QUOTA_MIN,
|
|
VIR_CGROUP_CPU_QUOTA_MAX);
|
|
return -1;
|
|
}
|
|
|
|
if (cfs_quota == VIR_CGROUP_CPU_QUOTA_MAX) {
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_CPU,
|
|
"cpu.max", "max");
|
|
}
|
|
|
|
return virCgroupSetValueI64(group,
|
|
VIR_CGROUP_CONTROLLER_CPU,
|
|
"cpu.max", cfs_quota);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetCpuCfsQuota(virCgroup *group,
|
|
long long *cfs_quota)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
char *tmp;
|
|
|
|
if (virCgroupGetValueStr(group, VIR_CGROUP_CONTROLLER_CPU,
|
|
"cpu.max", &str) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (STRPREFIX(str, "max")) {
|
|
*cfs_quota = VIR_CGROUP_CPU_QUOTA_MAX;
|
|
return 0;
|
|
}
|
|
|
|
if (virStrToLong_ll(str, &tmp, 10, cfs_quota) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to parse value '%s' from cpu.max."), str);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool
|
|
virCgroupV2SupportsCpuBW(virCgroup *cgroup)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
|
|
if (virCgroupV2PathOfController(cgroup, VIR_CGROUP_CONTROLLER_CPU,
|
|
"cpu.max", &path) < 0) {
|
|
virResetLastError();
|
|
return false;
|
|
}
|
|
|
|
return virFileExists(path);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetCpuacctUsage(virCgroup *group,
|
|
unsigned long long *usage)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
char *tmp;
|
|
|
|
if (virCgroupGetValueStr(group, VIR_CGROUP_CONTROLLER_CPUACCT,
|
|
"cpu.stat", &str) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!(tmp = strstr(str, "usage_usec "))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse cpu usage stat '%s'"), str);
|
|
return -1;
|
|
}
|
|
tmp += strlen("usage_usec ");
|
|
|
|
if (virStrToLong_ull(tmp, &tmp, 10, usage) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to parse value '%s' as number."), tmp);
|
|
return -1;
|
|
}
|
|
|
|
*usage *= 1000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetCpuacctStat(virCgroup *group,
|
|
unsigned long long *user,
|
|
unsigned long long *sys)
|
|
{
|
|
g_autofree char *str = NULL;
|
|
char *tmp;
|
|
unsigned long long userVal = 0;
|
|
unsigned long long sysVal = 0;
|
|
|
|
if (virCgroupGetValueStr(group, VIR_CGROUP_CONTROLLER_CPUACCT,
|
|
"cpu.stat", &str) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!(tmp = strstr(str, "user_usec "))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse cpu user stat '%s'"), str);
|
|
return -1;
|
|
}
|
|
tmp += strlen("user_usec ");
|
|
|
|
if (virStrToLong_ull(tmp, &tmp, 10, &userVal) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to parse value '%s' as number."), tmp);
|
|
return -1;
|
|
}
|
|
|
|
if (!(tmp = strstr(str, "system_usec "))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse cpu sys stat '%s'"), str);
|
|
return -1;
|
|
}
|
|
tmp += strlen("system_usec ");
|
|
|
|
if (virStrToLong_ull(tmp, &tmp, 10, &sysVal) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to parse value '%s' as number."), tmp);
|
|
return -1;
|
|
}
|
|
|
|
*user = userVal * 1000;
|
|
*sys = sysVal * 1000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetCpusetMems(virCgroup *group,
|
|
const char *mems)
|
|
{
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_CPUSET,
|
|
"cpuset.mems",
|
|
mems);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetCpusetMems(virCgroup *group,
|
|
char **mems)
|
|
{
|
|
return virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_CPUSET,
|
|
"cpuset.mems",
|
|
mems);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetCpusetMemoryMigrate(virCgroup *group G_GNUC_UNUSED,
|
|
bool migrate G_GNUC_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetCpusetMemoryMigrate(virCgroup *group G_GNUC_UNUSED,
|
|
bool *migrate)
|
|
{
|
|
*migrate = true;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2SetCpusetCpus(virCgroup *group,
|
|
const char *cpus)
|
|
{
|
|
return virCgroupSetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_CPUSET,
|
|
"cpuset.cpus",
|
|
cpus);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2GetCpusetCpus(virCgroup *group,
|
|
char **cpus)
|
|
{
|
|
return virCgroupGetValueStr(group,
|
|
VIR_CGROUP_CONTROLLER_CPUSET,
|
|
"cpuset.cpus",
|
|
cpus);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2AllowDevice(virCgroup *group,
|
|
char type,
|
|
int major,
|
|
int minor,
|
|
int perms)
|
|
{
|
|
uint64_t key = virCgroupV2DevicesGetKey(major, minor);
|
|
uint32_t val = virCgroupV2DevicesGetPerms(perms, type);
|
|
int rc;
|
|
|
|
if (virCgroupV2DevicesPrepareProg(group) < 0)
|
|
return -1;
|
|
|
|
rc = virBPFLookupElem(group->unified.devices.mapfd, &key, NULL);
|
|
|
|
if (virBPFUpdateElem(group->unified.devices.mapfd, &key, &val) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("failed to update device in BPF cgroup map"));
|
|
return -1;
|
|
}
|
|
|
|
if (rc < 0)
|
|
group->unified.devices.count++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2DenyDevice(virCgroup *group,
|
|
char type,
|
|
int major,
|
|
int minor,
|
|
int perms)
|
|
{
|
|
uint64_t key = virCgroupV2DevicesGetKey(major, minor);
|
|
uint32_t newval = virCgroupV2DevicesGetPerms(perms, type);
|
|
uint32_t val = 0;
|
|
|
|
if (virCgroupV2DevicesPrepareProg(group) < 0)
|
|
return -1;
|
|
|
|
if (group->unified.devices.count <= 0 ||
|
|
virBPFLookupElem(group->unified.devices.mapfd, &key, &val) < 0) {
|
|
VIR_DEBUG("nothing to do, device is not allowed");
|
|
return 0;
|
|
}
|
|
|
|
val = val & ~newval;
|
|
|
|
if (val == 0) {
|
|
if (virBPFDeleteElem(group->unified.devices.mapfd, &key) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("failed to remove device from BPF cgroup map"));
|
|
return -1;
|
|
}
|
|
group->unified.devices.count--;
|
|
} else {
|
|
if (virBPFUpdateElem(group->unified.devices.mapfd, &key, &val) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("failed to update device in BPF cgroup map"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2AllowAllDevices(virCgroup *group,
|
|
int perms)
|
|
{
|
|
if (virCgroupV2DevicesPrepareProg(group) < 0)
|
|
return -1;
|
|
|
|
if (group->unified.devices.count > 0 &&
|
|
perms == VIR_CGROUP_DEVICE_RWM &&
|
|
virCgroupV2DevicesCreateProg(group) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return virCgroupV2AllowDevice(group, 'a', -1, -1, perms);
|
|
}
|
|
|
|
|
|
static int
|
|
virCgroupV2DenyAllDevices(virCgroup *group)
|
|
{
|
|
if (virCgroupV2DevicesDetectProg(group) < 0)
|
|
return -1;
|
|
|
|
return virCgroupV2DevicesCreateProg(group);
|
|
}
|
|
|
|
|
|
virCgroupBackend virCgroupV2Backend = {
|
|
.type = VIR_CGROUP_BACKEND_TYPE_V2,
|
|
|
|
.available = virCgroupV2Available,
|
|
.validateMachineGroup = virCgroupV2ValidateMachineGroup,
|
|
.copyMounts = virCgroupV2CopyMounts,
|
|
.copyPlacement = virCgroupV2CopyPlacement,
|
|
.detectMounts = virCgroupV2DetectMounts,
|
|
.detectPlacement = virCgroupV2DetectPlacement,
|
|
.setPlacement = virCgroupV2SetPlacement,
|
|
.validatePlacement = virCgroupV2ValidatePlacement,
|
|
.stealPlacement = virCgroupV2StealPlacement,
|
|
.detectControllers = virCgroupV2DetectControllers,
|
|
.hasController = virCgroupV2HasController,
|
|
.getAnyController = virCgroupV2GetAnyController,
|
|
.pathOfController = virCgroupV2PathOfController,
|
|
.makeGroup = virCgroupV2MakeGroup,
|
|
.exists = virCgroupV2Exists,
|
|
.remove = virCgroupV2Remove,
|
|
.addTask = virCgroupV2AddTask,
|
|
.hasEmptyTasks = virCgroupV2HasEmptyTasks,
|
|
.killRecursive = virCgroupV2KillRecursive,
|
|
.bindMount = virCgroupV2BindMount,
|
|
.setOwner = virCgroupV2SetOwner,
|
|
|
|
.setBlkioWeight = virCgroupV2SetBlkioWeight,
|
|
.getBlkioWeight = virCgroupV2GetBlkioWeight,
|
|
.getBlkioIoServiced = virCgroupV2GetBlkioIoServiced,
|
|
.getBlkioIoDeviceServiced = virCgroupV2GetBlkioIoDeviceServiced,
|
|
.setBlkioDeviceWeight = virCgroupV2SetBlkioDeviceWeight,
|
|
.getBlkioDeviceWeight = virCgroupV2GetBlkioDeviceWeight,
|
|
.setBlkioDeviceReadIops = virCgroupV2SetBlkioDeviceReadIops,
|
|
.getBlkioDeviceReadIops = virCgroupV2GetBlkioDeviceReadIops,
|
|
.setBlkioDeviceWriteIops = virCgroupV2SetBlkioDeviceWriteIops,
|
|
.getBlkioDeviceWriteIops = virCgroupV2GetBlkioDeviceWriteIops,
|
|
.setBlkioDeviceReadBps = virCgroupV2SetBlkioDeviceReadBps,
|
|
.getBlkioDeviceReadBps = virCgroupV2GetBlkioDeviceReadBps,
|
|
.setBlkioDeviceWriteBps = virCgroupV2SetBlkioDeviceWriteBps,
|
|
.getBlkioDeviceWriteBps = virCgroupV2GetBlkioDeviceWriteBps,
|
|
|
|
.setMemory = virCgroupV2SetMemory,
|
|
.getMemoryStat = virCgroupV2GetMemoryStat,
|
|
.getMemoryUsage = virCgroupV2GetMemoryUsage,
|
|
.setMemoryHardLimit = virCgroupV2SetMemoryHardLimit,
|
|
.getMemoryHardLimit = virCgroupV2GetMemoryHardLimit,
|
|
.setMemorySoftLimit = virCgroupV2SetMemorySoftLimit,
|
|
.getMemorySoftLimit = virCgroupV2GetMemorySoftLimit,
|
|
.setMemSwapHardLimit = virCgroupV2SetMemSwapHardLimit,
|
|
.getMemSwapHardLimit = virCgroupV2GetMemSwapHardLimit,
|
|
.getMemSwapUsage = virCgroupV2GetMemSwapUsage,
|
|
|
|
.allowDevice = virCgroupV2AllowDevice,
|
|
.denyDevice = virCgroupV2DenyDevice,
|
|
.allowAllDevices = virCgroupV2AllowAllDevices,
|
|
.denyAllDevices = virCgroupV2DenyAllDevices,
|
|
|
|
.setCpuShares = virCgroupV2SetCpuShares,
|
|
.getCpuShares = virCgroupV2GetCpuShares,
|
|
.setCpuCfsPeriod = virCgroupV2SetCpuCfsPeriod,
|
|
.getCpuCfsPeriod = virCgroupV2GetCpuCfsPeriod,
|
|
.setCpuCfsQuota = virCgroupV2SetCpuCfsQuota,
|
|
.getCpuCfsQuota = virCgroupV2GetCpuCfsQuota,
|
|
.supportsCpuBW = virCgroupV2SupportsCpuBW,
|
|
|
|
.getCpuacctUsage = virCgroupV2GetCpuacctUsage,
|
|
.getCpuacctStat = virCgroupV2GetCpuacctStat,
|
|
|
|
.setCpusetMems = virCgroupV2SetCpusetMems,
|
|
.getCpusetMems = virCgroupV2GetCpusetMems,
|
|
.setCpusetMemoryMigrate = virCgroupV2SetCpusetMemoryMigrate,
|
|
.getCpusetMemoryMigrate = virCgroupV2GetCpusetMemoryMigrate,
|
|
.setCpusetCpus = virCgroupV2SetCpusetCpus,
|
|
.getCpusetCpus = virCgroupV2GetCpusetCpus,
|
|
};
|
|
|
|
|
|
void
|
|
virCgroupV2Register(void)
|
|
{
|
|
virCgroupBackendRegister(&virCgroupV2Backend);
|
|
}
|
|
|
|
#else /* !__linux__ */
|
|
|
|
void
|
|
virCgroupV2Register(void)
|
|
{
|
|
VIR_INFO("Control groups not supported on this platform");
|
|
}
|
|
|
|
#endif /* !__linux__ */
|