4cad4c
From f45cb6d6a2c247c7594d9965cbb745303adb61d6 Mon Sep 17 00:00:00 2001
4cad4c
From: Chris Down <chris@chrisdown.name>
4cad4c
Date: Thu, 28 Mar 2019 12:50:50 +0000
4cad4c
Subject: [PATCH] cgroup: Implement default propagation of MemoryLow with
4cad4c
 DefaultMemoryLow
4cad4c
4cad4c
In cgroup v2 we have protection tunables -- currently MemoryLow and
4cad4c
MemoryMin (there will be more in future for other resources, too). The
4cad4c
design of these protection tunables requires not only intermediate
4cad4c
cgroups to propagate protections, but also the units at the leaf of that
4cad4c
resource's operation to accept it (by setting MemoryLow or MemoryMin).
4cad4c
4cad4c
This makes sense from an low-level API design perspective, but it's a
4cad4c
good idea to also have a higher-level abstraction that can, by default,
4cad4c
propagate these resources to children recursively. In this patch, this
4cad4c
happens by having descendants set memory.low to N if their ancestor has
4cad4c
DefaultMemoryLow=N -- assuming they don't set a separate MemoryLow
4cad4c
value.
4cad4c
4cad4c
Any affected unit can opt out of this propagation by manually setting
4cad4c
`MemoryLow` to some value in its unit configuration. A unit can also
4cad4c
stop further propagation by setting `DefaultMemoryLow=` with no
4cad4c
argument. This removes further propagation in the subtree, but has no
4cad4c
effect on the unit itself (for that, use `MemoryLow=0`).
4cad4c
4cad4c
Our use case in production is simplifying the configuration of machines
4cad4c
which heavily rely on memory protection tunables, but currently require
4cad4c
tweaking a huge number of unit files to make that a reality. This
4cad4c
directive makes that significantly less fragile, and decreases the risk
4cad4c
of misconfiguration.
4cad4c
4cad4c
After this patch is merged, I will implement DefaultMemoryMin= using the
4cad4c
same principles.
4cad4c
4cad4c
(cherry picked from commit c52db42b78f6fbeb7792cc4eca27e2767a48b6ca)
4cad4c
4cad4c
Related: #1763435
4cad4c
---
4cad4c
 doc/TRANSIENT-SETTINGS.md             |   1 +
4cad4c
 man/systemd.resource-control.xml      |   4 +
4cad4c
 src/core/cgroup.c                     |  58 +++++++++--
4cad4c
 src/core/cgroup.h                     |   6 ++
4cad4c
 src/core/dbus-cgroup.c                |   7 ++
4cad4c
 src/core/load-fragment-gperf.gperf.m4 |   1 +
4cad4c
 src/core/load-fragment.c              |  13 ++-
4cad4c
 src/shared/bus-unit-util.c            |   2 +-
4cad4c
 src/shared/bus-util.c                 |   2 +-
4cad4c
 src/systemctl/systemctl.c             |   3 +
4cad4c
 src/test/meson.build                  |   6 ++
4cad4c
 src/test/test-cgroup-unit-default.c   | 145 ++++++++++++++++++++++++++
4cad4c
 test/dml-discard-empty.service        |   7 ++
4cad4c
 test/dml-discard-set-ml.service       |   8 ++
4cad4c
 test/dml-discard.slice                |   5 +
4cad4c
 test/dml-override-empty.service       |   7 ++
4cad4c
 test/dml-override.slice               |   5 +
4cad4c
 test/dml-passthrough-empty.service    |   7 ++
4cad4c
 test/dml-passthrough-set-dml.service  |   8 ++
4cad4c
 test/dml-passthrough-set-ml.service   |   8 ++
4cad4c
 test/dml-passthrough.slice            |   5 +
4cad4c
 test/dml.slice                        |   5 +
4cad4c
 test/meson.build                      |  10 ++
4cad4c
 23 files changed, 310 insertions(+), 13 deletions(-)
4cad4c
 create mode 100644 src/test/test-cgroup-unit-default.c
4cad4c
 create mode 100644 test/dml-discard-empty.service
4cad4c
 create mode 100644 test/dml-discard-set-ml.service
4cad4c
 create mode 100644 test/dml-discard.slice
4cad4c
 create mode 100644 test/dml-override-empty.service
4cad4c
 create mode 100644 test/dml-override.slice
4cad4c
 create mode 100644 test/dml-passthrough-empty.service
4cad4c
 create mode 100644 test/dml-passthrough-set-dml.service
4cad4c
 create mode 100644 test/dml-passthrough-set-ml.service
4cad4c
 create mode 100644 test/dml-passthrough.slice
4cad4c
 create mode 100644 test/dml.slice
4cad4c
4cad4c
diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md
4cad4c
index 93865c0333..5a8fa0727e 100644
4cad4c
--- a/doc/TRANSIENT-SETTINGS.md
4cad4c
+++ b/doc/TRANSIENT-SETTINGS.md
4cad4c
@@ -223,6 +223,7 @@ All cgroup/resource control settings are available for transient units
4cad4c
 ✓ AllowedMemoryNodes=
4cad4c
 ✓ MemoryAccounting=
4cad4c
 ✓ MemoryMin=
4cad4c
+✓ DefaultMemoryLow=
4cad4c
 ✓ MemoryLow=
4cad4c
 ✓ MemoryHigh=
4cad4c
 ✓ MemoryMax=
4cad4c
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
4cad4c
index 63a0c87565..27f16001dd 100644
4cad4c
--- a/man/systemd.resource-control.xml
4cad4c
+++ b/man/systemd.resource-control.xml
4cad4c
@@ -305,6 +305,10 @@
4cad4c
 
4cad4c
           <para>This setting is supported only if the unified control group hierarchy is used and disables
4cad4c
           <varname>MemoryLimit=</varname>.</para>
4cad4c
+
4cad4c
+          <para>Units may can have their children use a default <literal>memory.low</literal> value by specifying
4cad4c
+          <varname>DefaultMemoryLow=</varname>, which has the same usage as <varname>MemoryLow=</varname>. This setting
4cad4c
+          does not affect <literal>memory.low</literal> in the unit itself.</para>
4cad4c
         </listitem>
4cad4c
       </varlistentry>
4cad4c
 
4cad4c
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
4cad4c
index a17b38f914..f804bf4727 100644
4cad4c
--- a/src/core/cgroup.c
4cad4c
+++ b/src/core/cgroup.c
4cad4c
@@ -220,6 +220,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
4cad4c
                 "%sStartupIOWeight=%" PRIu64 "\n"
4cad4c
                 "%sBlockIOWeight=%" PRIu64 "\n"
4cad4c
                 "%sStartupBlockIOWeight=%" PRIu64 "\n"
4cad4c
+                "%sDefaultMemoryLow=%" PRIu64 "\n"
4cad4c
                 "%sMemoryMin=%" PRIu64 "\n"
4cad4c
                 "%sMemoryLow=%" PRIu64 "\n"
4cad4c
                 "%sMemoryHigh=%" PRIu64 "\n"
4cad4c
@@ -247,6 +248,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
4cad4c
                 prefix, c->startup_io_weight,
4cad4c
                 prefix, c->blockio_weight,
4cad4c
                 prefix, c->startup_blockio_weight,
4cad4c
+                prefix, c->default_memory_low,
4cad4c
                 prefix, c->memory_min,
4cad4c
                 prefix, c->memory_low,
4cad4c
                 prefix, c->memory_high,
4cad4c
@@ -370,6 +372,32 @@ int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode)
4cad4c
         return 0;
4cad4c
 }
4cad4c
 
4cad4c
+uint64_t unit_get_ancestor_memory_low(Unit *u) {
4cad4c
+        CGroupContext *c;
4cad4c
+
4cad4c
+        /* 1. Is MemoryLow set in this unit? If so, use that.
4cad4c
+         * 2. Is DefaultMemoryLow set in any ancestor? If so, use that.
4cad4c
+         * 3. Otherwise, return CGROUP_LIMIT_MIN. */
4cad4c
+
4cad4c
+        assert(u);
4cad4c
+
4cad4c
+        c = unit_get_cgroup_context(u);
4cad4c
+
4cad4c
+        if (c->memory_low_set)
4cad4c
+                return c->memory_low;
4cad4c
+
4cad4c
+        while (UNIT_ISSET(u->slice)) {
4cad4c
+                u = UNIT_DEREF(u->slice);
4cad4c
+                c = unit_get_cgroup_context(u);
4cad4c
+
4cad4c
+                if (c->default_memory_low_set)
4cad4c
+                        return c->default_memory_low;
4cad4c
+        }
4cad4c
+
4cad4c
+        /* We've reached the root, but nobody had DefaultMemoryLow set, so set it to the kernel default. */
4cad4c
+        return CGROUP_LIMIT_MIN;
4cad4c
+}
4cad4c
+
4cad4c
 static int lookup_block_device(const char *p, dev_t *ret) {
4cad4c
         struct stat st;
4cad4c
         int r;
4cad4c
@@ -807,8 +835,17 @@ static void cgroup_apply_blkio_device_limit(Unit *u, const char *dev_path, uint6
4cad4c
                               "Failed to set blkio.throttle.write_bps_device: %m");
4cad4c
 }
4cad4c
 
4cad4c
-static bool cgroup_context_has_unified_memory_config(CGroupContext *c) {
4cad4c
-        return c->memory_min > 0 || c->memory_low > 0 || c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX || c->memory_swap_max != CGROUP_LIMIT_MAX;
4cad4c
+static bool unit_has_unified_memory_config(Unit *u) {
4cad4c
+        CGroupContext *c;
4cad4c
+
4cad4c
+        assert(u);
4cad4c
+
4cad4c
+        c = unit_get_cgroup_context(u);
4cad4c
+        assert(c);
4cad4c
+
4cad4c
+        return c->memory_min > 0 || unit_get_ancestor_memory_low(u) > 0 ||
4cad4c
+               c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX ||
4cad4c
+               c->memory_swap_max != CGROUP_LIMIT_MAX;
4cad4c
 }
4cad4c
 
4cad4c
 static void cgroup_apply_unified_memory_limit(Unit *u, const char *file, uint64_t v) {
4cad4c
@@ -1056,7 +1093,7 @@ static void cgroup_context_apply(
4cad4c
                 if (cg_all_unified() > 0) {
4cad4c
                         uint64_t max, swap_max = CGROUP_LIMIT_MAX;
4cad4c
 
4cad4c
-                        if (cgroup_context_has_unified_memory_config(c)) {
4cad4c
+                        if (unit_has_unified_memory_config(u)) {
4cad4c
                                 max = c->memory_max;
4cad4c
                                 swap_max = c->memory_swap_max;
4cad4c
                         } else {
4cad4c
@@ -1067,7 +1104,7 @@ static void cgroup_context_apply(
4cad4c
                         }
4cad4c
 
4cad4c
                         cgroup_apply_unified_memory_limit(u, "memory.min", c->memory_min);
4cad4c
-                        cgroup_apply_unified_memory_limit(u, "memory.low", c->memory_low);
4cad4c
+                        cgroup_apply_unified_memory_limit(u, "memory.low", unit_get_ancestor_memory_low(u));
4cad4c
                         cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high);
4cad4c
                         cgroup_apply_unified_memory_limit(u, "memory.max", max);
4cad4c
                         cgroup_apply_unified_memory_limit(u, "memory.swap.max", swap_max);
4cad4c
@@ -1075,7 +1112,7 @@ static void cgroup_context_apply(
4cad4c
                         char buf[DECIMAL_STR_MAX(uint64_t) + 1];
4cad4c
                         uint64_t val;
4cad4c
 
4cad4c
-                        if (cgroup_context_has_unified_memory_config(c)) {
4cad4c
+                        if (unit_has_unified_memory_config(u)) {
4cad4c
                                 val = c->memory_max;
4cad4c
                                 log_cgroup_compat(u, "Applying MemoryMax %" PRIi64 " as MemoryLimit", val);
4cad4c
                         } else
4cad4c
@@ -1204,8 +1241,13 @@ static void cgroup_context_apply(
4cad4c
                 cgroup_apply_firewall(u);
4cad4c
 }
4cad4c
 
4cad4c
-CGroupMask cgroup_context_get_mask(CGroupContext *c) {
4cad4c
+static CGroupMask unit_get_cgroup_mask(Unit *u) {
4cad4c
         CGroupMask mask = 0;
4cad4c
+        CGroupContext *c;
4cad4c
+
4cad4c
+        assert(u);
4cad4c
+
4cad4c
+        c = unit_get_cgroup_context(u);
4cad4c
 
4cad4c
         /* Figure out which controllers we need */
4cad4c
 
4cad4c
@@ -1223,7 +1265,7 @@ CGroupMask cgroup_context_get_mask(CGroupContext *c) {
4cad4c
 
4cad4c
         if (c->memory_accounting ||
4cad4c
             c->memory_limit != CGROUP_LIMIT_MAX ||
4cad4c
-            cgroup_context_has_unified_memory_config(c))
4cad4c
+            unit_has_unified_memory_config(u))
4cad4c
                 mask |= CGROUP_MASK_MEMORY;
4cad4c
 
4cad4c
         if (c->device_allow ||
4cad4c
@@ -1246,7 +1288,7 @@ CGroupMask unit_get_own_mask(Unit *u) {
4cad4c
         if (!c)
4cad4c
                 return 0;
4cad4c
 
4cad4c
-        return cgroup_context_get_mask(c) | unit_get_delegate_mask(u);
4cad4c
+        return unit_get_cgroup_mask(u) | unit_get_delegate_mask(u);
4cad4c
 }
4cad4c
 
4cad4c
 CGroupMask unit_get_delegate_mask(Unit *u) {
4cad4c
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
4cad4c
index 8102b442b8..a263d6a169 100644
4cad4c
--- a/src/core/cgroup.h
4cad4c
+++ b/src/core/cgroup.h
4cad4c
@@ -95,12 +95,16 @@ struct CGroupContext {
4cad4c
         LIST_HEAD(CGroupIODeviceLimit, io_device_limits);
4cad4c
         LIST_HEAD(CGroupIODeviceLatency, io_device_latencies);
4cad4c
 
4cad4c
+        uint64_t default_memory_low;
4cad4c
         uint64_t memory_min;
4cad4c
         uint64_t memory_low;
4cad4c
         uint64_t memory_high;
4cad4c
         uint64_t memory_max;
4cad4c
         uint64_t memory_swap_max;
4cad4c
 
4cad4c
+        bool default_memory_low_set;
4cad4c
+        bool memory_low_set;
4cad4c
+
4cad4c
         LIST_HEAD(IPAddressAccessItem, ip_address_allow);
4cad4c
         LIST_HEAD(IPAddressAccessItem, ip_address_deny);
4cad4c
 
4cad4c
@@ -191,6 +195,8 @@ Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup);
4cad4c
 Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid);
4cad4c
 Unit* manager_get_unit_by_pid(Manager *m, pid_t pid);
4cad4c
 
4cad4c
+uint64_t unit_get_ancestor_memory_low(Unit *u);
4cad4c
+
4cad4c
 int unit_search_main_pid(Unit *u, pid_t *ret);
4cad4c
 int unit_watch_all_pids(Unit *u);
4cad4c
 
4cad4c
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
4cad4c
index 6ce5984a02..2115d43b0c 100644
4cad4c
--- a/src/core/dbus-cgroup.c
4cad4c
+++ b/src/core/dbus-cgroup.c
4cad4c
@@ -353,6 +353,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
4cad4c
         SD_BUS_PROPERTY("BlockIOReadBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0),
4cad4c
         SD_BUS_PROPERTY("BlockIOWriteBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0),
4cad4c
         SD_BUS_PROPERTY("MemoryAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, memory_accounting), 0),
4cad4c
+        SD_BUS_PROPERTY("DefaultMemoryLow", "t", NULL, offsetof(CGroupContext, default_memory_low), 0),
4cad4c
         SD_BUS_PROPERTY("MemoryMin", "t", NULL, offsetof(CGroupContext, memory_min), 0),
4cad4c
         SD_BUS_PROPERTY("MemoryLow", "t", NULL, offsetof(CGroupContext, memory_low), 0),
4cad4c
         SD_BUS_PROPERTY("MemoryHigh", "t", NULL, offsetof(CGroupContext, memory_high), 0),
4cad4c
@@ -668,6 +669,9 @@ int bus_cgroup_set_property(
4cad4c
         if (streq(name, "MemoryLow"))
4cad4c
                 return bus_cgroup_set_memory(u, name, &c->memory_low, message, flags, error);
4cad4c
 
4cad4c
+        if (streq(name, "DefaultMemoryLow"))
4cad4c
+                return bus_cgroup_set_memory(u, name, &c->default_memory_low, message, flags, error);
4cad4c
+
4cad4c
         if (streq(name, "MemoryHigh"))
4cad4c
                 return bus_cgroup_set_memory(u, name, &c->memory_high, message, flags, error);
4cad4c
 
4cad4c
@@ -686,6 +690,9 @@ int bus_cgroup_set_property(
4cad4c
         if (streq(name, "MemoryLowScale"))
4cad4c
                 return bus_cgroup_set_memory_scale(u, name, &c->memory_low, message, flags, error);
4cad4c
 
4cad4c
+        if (streq(name, "DefaultMemoryLowScale"))
4cad4c
+                return bus_cgroup_set_memory_scale(u, name, &c->default_memory_low, message, flags, error);
4cad4c
+
4cad4c
         if (streq(name, "MemoryHighScale"))
4cad4c
                 return bus_cgroup_set_memory_scale(u, name, &c->memory_high, message, flags, error);
4cad4c
 
4cad4c
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
4cad4c
index 1c6e539f30..43cc78fdea 100644
4cad4c
--- a/src/core/load-fragment-gperf.gperf.m4
4cad4c
+++ b/src/core/load-fragment-gperf.gperf.m4
4cad4c
@@ -172,6 +172,7 @@ $1.CPUQuota,                     config_parse_cpu_quota,             0,
4cad4c
 $1.CPUQuotaPeriodSec,            config_parse_sec_def_infinity,      0,                             offsetof($1, cgroup_context.cpu_quota_period_usec)
4cad4c
 $1.MemoryAccounting,             config_parse_bool,                  0,                             offsetof($1, cgroup_context.memory_accounting)
4cad4c
 $1.MemoryMin,                    config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
4cad4c
+$1.DefaultMemoryLow,             config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
4cad4c
 $1.MemoryLow,                    config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
4cad4c
 $1.MemoryHigh,                   config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
4cad4c
 $1.MemoryMax,                    config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
4cad4c
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
4cad4c
index 89a3457acc..20faed02ad 100644
4cad4c
--- a/src/core/load-fragment.c
4cad4c
+++ b/src/core/load-fragment.c
4cad4c
@@ -3096,11 +3096,18 @@ int config_parse_memory_limit(
4cad4c
                 }
4cad4c
         }
4cad4c
 
4cad4c
-        if (streq(lvalue, "MemoryMin"))
4cad4c
+        if (streq(lvalue, "DefaultMemoryLow")) {
4cad4c
+                c->default_memory_low_set = true;
4cad4c
+                if (isempty(rvalue))
4cad4c
+                        c->default_memory_low = CGROUP_LIMIT_MIN;
4cad4c
+                else
4cad4c
+                        c->default_memory_low = bytes;
4cad4c
+        } else if (streq(lvalue, "MemoryMin"))
4cad4c
                 c->memory_min = bytes;
4cad4c
-        else if (streq(lvalue, "MemoryLow"))
4cad4c
+        else if (streq(lvalue, "MemoryLow")) {
4cad4c
                 c->memory_low = bytes;
4cad4c
-        else if (streq(lvalue, "MemoryHigh"))
4cad4c
+                c->memory_low_set = true;
4cad4c
+        } else if (streq(lvalue, "MemoryHigh"))
4cad4c
                 c->memory_high = bytes;
4cad4c
         else if (streq(lvalue, "MemoryMax"))
4cad4c
                 c->memory_max = bytes;
4cad4c
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
4cad4c
index 203c068d02..f88730a85d 100644
4cad4c
--- a/src/shared/bus-unit-util.c
4cad4c
+++ b/src/shared/bus-unit-util.c
4cad4c
@@ -429,7 +429,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
4cad4c
                 return 1;
4cad4c
         }
4cad4c
 
4cad4c
-        if (STR_IN_SET(field, "MemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) {
4cad4c
+        if (STR_IN_SET(field, "MemoryMin", "DefaultMemoryLow", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) {
4cad4c
 
4cad4c
                 if (isempty(eq) || streq(eq, "infinity")) {
4cad4c
                         r = sd_bus_message_append(m, "(sv)", field, "t", CGROUP_LIMIT_MAX);
4cad4c
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
4cad4c
index 5ed68429be..0ba2712deb 100644
4cad4c
--- a/src/shared/bus-util.c
4cad4c
+++ b/src/shared/bus-util.c
4cad4c
@@ -774,7 +774,7 @@ int bus_print_property(const char *name, sd_bus_message *m, bool value, bool all
4cad4c
 
4cad4c
                         print_property(name, "%s", "[not set]");
4cad4c
 
4cad4c
-                else if ((STR_IN_SET(name, "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) ||
4cad4c
+                else if ((STR_IN_SET(name, "DefaultMemoryLow", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) ||
4cad4c
                          (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == (uint64_t) -1) ||
4cad4c
                          (startswith(name, "Limit") && u == (uint64_t) -1) ||
4cad4c
                          (startswith(name, "DefaultLimit") && u == (uint64_t) -1))
4cad4c
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
4cad4c
index 35ad20f510..763ca0c6b7 100644
4cad4c
--- a/src/systemctl/systemctl.c
4cad4c
+++ b/src/systemctl/systemctl.c
4cad4c
@@ -3918,6 +3918,8 @@ typedef struct UnitStatusInfo {
4cad4c
         uint64_t ip_ingress_bytes;
4cad4c
         uint64_t ip_egress_bytes;
4cad4c
 
4cad4c
+        uint64_t default_memory_low;
4cad4c
+
4cad4c
         LIST_HEAD(ExecStatusInfo, exec);
4cad4c
 } UnitStatusInfo;
4cad4c
 
4cad4c
@@ -5028,6 +5030,7 @@ static int show_one(
4cad4c
                 { "Where",                          "s",              NULL,           offsetof(UnitStatusInfo, where)                             },
4cad4c
                 { "What",                           "s",              NULL,           offsetof(UnitStatusInfo, what)                              },
4cad4c
                 { "MemoryCurrent",                  "t",              NULL,           offsetof(UnitStatusInfo, memory_current)                    },
4cad4c
+                { "DefaultMemoryLow",               "t",              NULL,           offsetof(UnitStatusInfo, default_memory_low)                },
4cad4c
                 { "MemoryMin",                      "t",              NULL,           offsetof(UnitStatusInfo, memory_min)                        },
4cad4c
                 { "MemoryLow",                      "t",              NULL,           offsetof(UnitStatusInfo, memory_low)                        },
4cad4c
                 { "MemoryHigh",                     "t",              NULL,           offsetof(UnitStatusInfo, memory_high)                       },
4cad4c
diff --git a/src/test/meson.build b/src/test/meson.build
4cad4c
index 22264d034c..7b310d4ec7 100644
4cad4c
--- a/src/test/meson.build
4cad4c
+++ b/src/test/meson.build
4cad4c
@@ -518,6 +518,12 @@ tests += [
4cad4c
           libshared],
4cad4c
          []],
4cad4c
 
4cad4c
+        [['src/test/test-cgroup-unit-default.c',
4cad4c
+          'src/test/test-helper.c'],
4cad4c
+         [libcore,
4cad4c
+          libshared],
4cad4c
+         []],
4cad4c
+
4cad4c
         [['src/test/test-cgroup-mask.c',
4cad4c
           'src/test/test-helper.c'],
4cad4c
          [libcore,
4cad4c
diff --git a/src/test/test-cgroup-unit-default.c b/src/test/test-cgroup-unit-default.c
4cad4c
new file mode 100644
4cad4c
index 0000000000..54f7d50c45
4cad4c
--- /dev/null
4cad4c
+++ b/src/test/test-cgroup-unit-default.c
4cad4c
@@ -0,0 +1,145 @@
4cad4c
+/* SPDX-License-Identifier: LGPL-2.1+ */
4cad4c
+
4cad4c
+#include <stdio.h>
4cad4c
+
4cad4c
+#include "cgroup.h"
4cad4c
+#include "manager.h"
4cad4c
+#include "rm-rf.h"
4cad4c
+#include "test-helper.h"
4cad4c
+#include "tests.h"
4cad4c
+#include "unit.h"
4cad4c
+
4cad4c
+static int test_default_memory_low(void) {
4cad4c
+        _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
4cad4c
+        _cleanup_(manager_freep) Manager *m = NULL;
4cad4c
+        Unit *root, *dml,
4cad4c
+             *dml_passthrough, *dml_passthrough_empty, *dml_passthrough_set_dml, *dml_passthrough_set_ml,
4cad4c
+             *dml_override, *dml_override_empty,
4cad4c
+             *dml_discard, *dml_discard_empty, *dml_discard_set_ml;
4cad4c
+        uint64_t dml_tree_default;
4cad4c
+        int r;
4cad4c
+
4cad4c
+        r = enter_cgroup_subroot();
4cad4c
+        if (r == -ENOMEDIUM)
4cad4c
+                return EXIT_TEST_SKIP;
4cad4c
+
4cad4c
+        assert_se(set_unit_path(get_testdata_dir()) >= 0);
4cad4c
+        assert_se(runtime_dir = setup_fake_runtime_dir());
4cad4c
+        r = manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m);
4cad4c
+        if (IN_SET(r, -EPERM, -EACCES)) {
4cad4c
+                log_error_errno(r, "manager_new: %m");
4cad4c
+                return EXIT_TEST_SKIP;
4cad4c
+        }
4cad4c
+
4cad4c
+        assert_se(r >= 0);
4cad4c
+        assert_se(manager_startup(m, NULL, NULL) >= 0);
4cad4c
+
4cad4c
+        /* dml.slice has DefaultMemoryLow=50. Beyond that, individual subhierarchies look like this:
4cad4c
+         *
4cad4c
+         * 1. dml-passthrough.slice sets MemoryLow=100. This should not affect its children, as only
4cad4c
+         *    DefaultMemoryLow is propagated, not MemoryLow. As such, all leaf services should end up with
4cad4c
+         *    memory.low as 50, inherited from dml.slice, *except* for dml-passthrough-set-ml.service, which
4cad4c
+         *    should have the value of 25, as it has MemoryLow explicitly set.
4cad4c
+         *
4cad4c
+         *                                                  ┌───────────┐
4cad4c
+         *                                                  │ dml.slice │
4cad4c
+         *                                                  └─────┬─────┘
4cad4c
+         *                                                  MemoryLow=100
4cad4c
+         *                                            ┌───────────┴───────────┐
4cad4c
+         *                                            │ dml-passthrough.slice │
4cad4c
+         *                                            └───────────┬───────────┘
4cad4c
+         *                    ┌───────────────────────────────────┼───────────────────────────────────┐
4cad4c
+         *             no new settings                   DefaultMemoryLow=15                     MemoryLow=25
4cad4c
+         *    ┌───────────────┴───────────────┐  ┌────────────────┴────────────────┐  ┌───────────────┴────────────────┐
4cad4c
+         *    │ dml-passthrough-empty.service │  │ dml-passthrough-set-dml.service │  │ dml-passthrough-set-ml.service │
4cad4c
+         *    └───────────────────────────────┘  └─────────────────────────────────┘  └────────────────────────────────┘
4cad4c
+         *
4cad4c
+         * 2. dml-override.slice sets DefaultMemoryLow=10. As such, dml-override-empty.service should also
4cad4c
+         *    end up with a memory.low of 10. dml-override.slice should still have a memory.low of 50.
4cad4c
+         *
4cad4c
+         *            ┌───────────┐
4cad4c
+         *            │ dml.slice │
4cad4c
+         *            └─────┬─────┘
4cad4c
+         *         DefaultMemoryLow=10
4cad4c
+         *        ┌─────────┴──────────┐
4cad4c
+         *        │ dml-override.slice │
4cad4c
+         *        └─────────┬──────────┘
4cad4c
+         *           no new settings
4cad4c
+         *    ┌─────────────┴──────────────┐
4cad4c
+         *    │ dml-override-empty.service │
4cad4c
+         *    └────────────────────────────┘
4cad4c
+         *
4cad4c
+         * 3. dml-discard.slice sets DefaultMemoryLow= with no rvalue. As such,
4cad4c
+         *    dml-discard-empty.service should end up with a value of 0.
4cad4c
+         *    dml-discard-explicit-ml.service sets MemoryLow=70, and as such should have that override the
4cad4c
+         *    reset DefaultMemoryLow value. dml-discard.slice should still have an eventual memory.low of 50.
4cad4c
+         *
4cad4c
+         *                           ┌───────────┐
4cad4c
+         *                           │ dml.slice │
4cad4c
+         *                           └─────┬─────┘
4cad4c
+         *                         DefaultMemoryLow=
4cad4c
+         *                       ┌─────────┴─────────┐
4cad4c
+         *                       │ dml-discard.slice │
4cad4c
+         *                       └─────────┬─────────┘
4cad4c
+         *                  ┌──────────────┴───────────────┐
4cad4c
+         *           no new settings                  MemoryLow=15
4cad4c
+         *    ┌─────────────┴─────────────┐  ┌─────────────┴──────────────┐
4cad4c
+         *    │ dml-discard-empty.service │  │ dml-discard-set-ml.service │
4cad4c
+         *    └───────────────────────────┘  └────────────────────────────┘
4cad4c
+         */
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml.slice", NULL, &dml) >= 0);
4cad4c
+
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough.slice", NULL, &dml_passthrough) >= 0);
4cad4c
+        assert_se(UNIT_DEREF(dml_passthrough->slice) == dml);
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-empty.service", NULL, &dml_passthrough_empty) >= 0);
4cad4c
+        assert_se(UNIT_DEREF(dml_passthrough_empty->slice) == dml_passthrough);
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-dml.service", NULL, &dml_passthrough_set_dml) >= 0);
4cad4c
+        assert_se(UNIT_DEREF(dml_passthrough_set_dml->slice) == dml_passthrough);
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-ml.service", NULL, &dml_passthrough_set_ml) >= 0);
4cad4c
+        assert_se(UNIT_DEREF(dml_passthrough_set_ml->slice) == dml_passthrough);
4cad4c
+
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-override.slice", NULL, &dml_override) >= 0);
4cad4c
+        assert_se(UNIT_DEREF(dml_override->slice) == dml);
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-override-empty.service", NULL, &dml_override_empty) >= 0);
4cad4c
+        assert_se(UNIT_DEREF(dml_override_empty->slice) == dml_override);
4cad4c
+
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-discard.slice", NULL, &dml_discard) >= 0);
4cad4c
+        assert_se(UNIT_DEREF(dml_discard->slice) == dml);
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-empty.service", NULL, &dml_discard_empty) >= 0);
4cad4c
+        assert_se(UNIT_DEREF(dml_discard_empty->slice) == dml_discard);
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-set-ml.service", NULL, &dml_discard_set_ml) >= 0);
4cad4c
+        assert_se(UNIT_DEREF(dml_discard_set_ml->slice) == dml_discard);
4cad4c
+
4cad4c
+        root = UNIT_DEREF(dml->slice);
4cad4c
+        assert_se(!UNIT_ISSET(root->slice));
4cad4c
+
4cad4c
+        assert_se(unit_get_ancestor_memory_low(root) == CGROUP_LIMIT_MIN);
4cad4c
+
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml) == CGROUP_LIMIT_MIN);
4cad4c
+        dml_tree_default = unit_get_cgroup_context(dml)->default_memory_low;
4cad4c
+        assert_se(dml_tree_default == 50);
4cad4c
+
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml_passthrough) == 100);
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml_passthrough_empty) == dml_tree_default);
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_dml) == 50);
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_ml) == 25);
4cad4c
+
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml_override) == dml_tree_default);
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml_override_empty) == 10);
4cad4c
+
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml_discard) == dml_tree_default);
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml_discard_empty) == CGROUP_LIMIT_MIN);
4cad4c
+        assert_se(unit_get_ancestor_memory_low(dml_discard_set_ml) == 15);
4cad4c
+
4cad4c
+        return 0;
4cad4c
+}
4cad4c
+
4cad4c
+int main(int argc, char* argv[]) {
4cad4c
+        int rc = EXIT_SUCCESS;
4cad4c
+
4cad4c
+        test_setup_logging(LOG_DEBUG);
4cad4c
+
4cad4c
+        TEST_REQ_RUNNING_SYSTEMD(rc = test_default_memory_low());
4cad4c
+
4cad4c
+        return rc;
4cad4c
+}
4cad4c
diff --git a/test/dml-discard-empty.service b/test/dml-discard-empty.service
4cad4c
new file mode 100644
4cad4c
index 0000000000..75228f6470
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml-discard-empty.service
4cad4c
@@ -0,0 +1,7 @@
4cad4c
+[Unit]
4cad4c
+Description=DML discard empty service
4cad4c
+
4cad4c
+[Service]
4cad4c
+Slice=dml-discard.slice
4cad4c
+Type=oneshot
4cad4c
+ExecStart=/bin/true
4cad4c
diff --git a/test/dml-discard-set-ml.service b/test/dml-discard-set-ml.service
4cad4c
new file mode 100644
4cad4c
index 0000000000..591c99270c
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml-discard-set-ml.service
4cad4c
@@ -0,0 +1,8 @@
4cad4c
+[Unit]
4cad4c
+Description=DML discard set ml service
4cad4c
+
4cad4c
+[Service]
4cad4c
+Slice=dml-discard.slice
4cad4c
+Type=oneshot
4cad4c
+ExecStart=/bin/true
4cad4c
+MemoryLow=15
4cad4c
diff --git a/test/dml-discard.slice b/test/dml-discard.slice
4cad4c
new file mode 100644
4cad4c
index 0000000000..e26d86846c
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml-discard.slice
4cad4c
@@ -0,0 +1,5 @@
4cad4c
+[Unit]
4cad4c
+Description=DML discard slice
4cad4c
+
4cad4c
+[Slice]
4cad4c
+DefaultMemoryLow=
4cad4c
diff --git a/test/dml-override-empty.service b/test/dml-override-empty.service
4cad4c
new file mode 100644
4cad4c
index 0000000000..142c98720c
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml-override-empty.service
4cad4c
@@ -0,0 +1,7 @@
4cad4c
+[Unit]
4cad4c
+Description=DML override empty service
4cad4c
+
4cad4c
+[Service]
4cad4c
+Slice=dml-override.slice
4cad4c
+Type=oneshot
4cad4c
+ExecStart=/bin/true
4cad4c
diff --git a/test/dml-override.slice b/test/dml-override.slice
4cad4c
new file mode 100644
4cad4c
index 0000000000..feb6773e39
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml-override.slice
4cad4c
@@ -0,0 +1,5 @@
4cad4c
+[Unit]
4cad4c
+Description=DML override slice
4cad4c
+
4cad4c
+[Slice]
4cad4c
+DefaultMemoryLow=10
4cad4c
diff --git a/test/dml-passthrough-empty.service b/test/dml-passthrough-empty.service
4cad4c
new file mode 100644
4cad4c
index 0000000000..34832de491
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml-passthrough-empty.service
4cad4c
@@ -0,0 +1,7 @@
4cad4c
+[Unit]
4cad4c
+Description=DML passthrough empty service
4cad4c
+
4cad4c
+[Service]
4cad4c
+Slice=dml-passthrough.slice
4cad4c
+Type=oneshot
4cad4c
+ExecStart=/bin/true
4cad4c
diff --git a/test/dml-passthrough-set-dml.service b/test/dml-passthrough-set-dml.service
4cad4c
new file mode 100644
4cad4c
index 0000000000..5bdf4ed4b7
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml-passthrough-set-dml.service
4cad4c
@@ -0,0 +1,8 @@
4cad4c
+[Unit]
4cad4c
+Description=DML passthrough set DML service
4cad4c
+
4cad4c
+[Service]
4cad4c
+Slice=dml-passthrough.slice
4cad4c
+Type=oneshot
4cad4c
+ExecStart=/bin/true
4cad4c
+DefaultMemoryLow=15
4cad4c
diff --git a/test/dml-passthrough-set-ml.service b/test/dml-passthrough-set-ml.service
4cad4c
new file mode 100644
4cad4c
index 0000000000..2abd591389
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml-passthrough-set-ml.service
4cad4c
@@ -0,0 +1,8 @@
4cad4c
+[Unit]
4cad4c
+Description=DML passthrough set ML service
4cad4c
+
4cad4c
+[Service]
4cad4c
+Slice=dml-passthrough.slice
4cad4c
+Type=oneshot
4cad4c
+ExecStart=/bin/true
4cad4c
+MemoryLow=25
4cad4c
diff --git a/test/dml-passthrough.slice b/test/dml-passthrough.slice
4cad4c
new file mode 100644
4cad4c
index 0000000000..1b1a848edb
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml-passthrough.slice
4cad4c
@@ -0,0 +1,5 @@
4cad4c
+[Unit]
4cad4c
+Description=DML passthrough slice
4cad4c
+
4cad4c
+[Slice]
4cad4c
+MemoryLow=100
4cad4c
diff --git a/test/dml.slice b/test/dml.slice
4cad4c
new file mode 100644
4cad4c
index 0000000000..84e333ef04
4cad4c
--- /dev/null
4cad4c
+++ b/test/dml.slice
4cad4c
@@ -0,0 +1,5 @@
4cad4c
+[Unit]
4cad4c
+Description=DML slice
4cad4c
+
4cad4c
+[Slice]
4cad4c
+DefaultMemoryLow=50
4cad4c
diff --git a/test/meson.build b/test/meson.build
4cad4c
index 070731c4a9..52e4fa2e3c 100644
4cad4c
--- a/test/meson.build
4cad4c
+++ b/test/meson.build
4cad4c
@@ -7,6 +7,16 @@ test_data_files = '''
4cad4c
         c.service
4cad4c
         d.service
4cad4c
         daughter.service
4cad4c
+        dml.slice
4cad4c
+        dml-passthrough.slice
4cad4c
+        dml-passthrough-empty.service
4cad4c
+        dml-passthrough-set-dml.service
4cad4c
+        dml-passthrough-set-ml.service
4cad4c
+        dml-override.slice
4cad4c
+        dml-override-empty.service
4cad4c
+        dml-discard.slice
4cad4c
+        dml-discard-empty.service
4cad4c
+        dml-discard-set-ml.service
4cad4c
         e.service
4cad4c
         end.service
4cad4c
         f.service