]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cgroup: Implement default propagation of MemoryLow with DefaultMemoryLow
authorChris Down <chris@chrisdown.name>
Thu, 28 Mar 2019 12:50:50 +0000 (12:50 +0000)
committerLennart Poettering <lennart@poettering.net>
Fri, 12 Apr 2019 15:23:58 +0000 (17:23 +0200)
In cgroup v2 we have protection tunables -- currently MemoryLow and
MemoryMin (there will be more in future for other resources, too). The
design of these protection tunables requires not only intermediate
cgroups to propagate protections, but also the units at the leaf of that
resource's operation to accept it (by setting MemoryLow or MemoryMin).

This makes sense from an low-level API design perspective, but it's a
good idea to also have a higher-level abstraction that can, by default,
propagate these resources to children recursively. In this patch, this
happens by having descendants set memory.low to N if their ancestor has
DefaultMemoryLow=N -- assuming they don't set a separate MemoryLow
value.

Any affected unit can opt out of this propagation by manually setting
`MemoryLow` to some value in its unit configuration. A unit can also
stop further propagation by setting `DefaultMemoryLow=` with no
argument. This removes further propagation in the subtree, but has no
effect on the unit itself (for that, use `MemoryLow=0`).

Our use case in production is simplifying the configuration of machines
which heavily rely on memory protection tunables, but currently require
tweaking a huge number of unit files to make that a reality. This
directive makes that significantly less fragile, and decreases the risk
of misconfiguration.

After this patch is merged, I will implement DefaultMemoryMin= using the
same principles.

23 files changed:
docs/TRANSIENT-SETTINGS.md
man/systemd.resource-control.xml
src/core/cgroup.c
src/core/cgroup.h
src/core/dbus-cgroup.c
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/shared/bus-unit-util.c
src/shared/bus-util.c
src/systemctl/systemctl.c
src/test/meson.build
src/test/test-cgroup-unit-default.c [new file with mode: 0644]
test/dml-discard-empty.service [new file with mode: 0644]
test/dml-discard-set-ml.service [new file with mode: 0644]
test/dml-discard.slice [new file with mode: 0644]
test/dml-override-empty.service [new file with mode: 0644]
test/dml-override.slice [new file with mode: 0644]
test/dml-passthrough-empty.service [new file with mode: 0644]
test/dml-passthrough-set-dml.service [new file with mode: 0644]
test/dml-passthrough-set-ml.service [new file with mode: 0644]
test/dml-passthrough.slice [new file with mode: 0644]
test/dml.slice [new file with mode: 0644]
test/meson.build

index f081fdb2cee9dce83e1c333092f33dd5eb0ff10b..506bef92552ba68f598490d62a8455132a77e115 100644 (file)
@@ -228,6 +228,7 @@ All cgroup/resource control settings are available for transient units
 ✓ CPUQuotaPeriodSec=
 ✓ MemoryAccounting=
 ✓ MemoryMin=
+✓ DefaultMemoryLow=
 ✓ MemoryLow=
 ✓ MemoryHigh=
 ✓ MemoryMax=
index 4a8c57f45ace5a76e94715945985b43b87dfc7ee..e7fb46873c3af24d3654e73b2ddd30aacad0ab90 100644 (file)
 
           <para>This setting is supported only if the unified control group hierarchy is used and disables
           <varname>MemoryLimit=</varname>.</para>
+
+          <para>Units may can have their children use a default <literal>memory.low</literal> value by specifying
+          <varname>DefaultMemoryLow=</varname>, which has the same usage as <varname>MemoryLow=</varname>. This setting
+          does not affect <literal>memory.low</literal> in the unit itself.</para>
         </listitem>
       </varlistentry>
 
index 6a477adb71ffb3e3cd3c941e3554ef127c36d07f..ceb7ee21892d2debdeb170fcb71c0464a4deb9f9 100644 (file)
@@ -234,6 +234,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
                 "%sStartupIOWeight=%" PRIu64 "\n"
                 "%sBlockIOWeight=%" PRIu64 "\n"
                 "%sStartupBlockIOWeight=%" PRIu64 "\n"
+                "%sDefaultMemoryLow=%" PRIu64 "\n"
                 "%sMemoryMin=%" PRIu64 "\n"
                 "%sMemoryLow=%" PRIu64 "\n"
                 "%sMemoryHigh=%" PRIu64 "\n"
@@ -259,6 +260,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
                 prefix, c->startup_io_weight,
                 prefix, c->blockio_weight,
                 prefix, c->startup_blockio_weight,
+                prefix, c->default_memory_low,
                 prefix, c->memory_min,
                 prefix, c->memory_low,
                 prefix, c->memory_high,
@@ -382,6 +384,32 @@ int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode)
         return 0;
 }
 
+uint64_t unit_get_ancestor_memory_low(Unit *u) {
+        CGroupContext *c;
+
+        /* 1. Is MemoryLow set in this unit? If so, use that.
+         * 2. Is DefaultMemoryLow set in any ancestor? If so, use that.
+         * 3. Otherwise, return CGROUP_LIMIT_MIN. */
+
+        assert(u);
+
+        c = unit_get_cgroup_context(u);
+
+        if (c->memory_low_set)
+                return c->memory_low;
+
+        while (UNIT_ISSET(u->slice)) {
+                u = UNIT_DEREF(u->slice);
+                c = unit_get_cgroup_context(u);
+
+                if (c->default_memory_low_set)
+                        return c->default_memory_low;
+        }
+
+        /* We've reached the root, but nobody had DefaultMemoryLow set, so set it to the kernel default. */
+        return CGROUP_LIMIT_MIN;
+}
+
 static void cgroup_xattr_apply(Unit *u) {
         char ids[SD_ID128_STRING_MAX];
         int r;
@@ -877,8 +905,17 @@ static void cgroup_apply_blkio_device_limit(Unit *u, const char *dev_path, uint6
         (void) set_attribute_and_warn(u, "blkio", "blkio.throttle.write_bps_device", buf);
 }
 
-static bool cgroup_context_has_unified_memory_config(CGroupContext *c) {
-        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;
+static bool unit_has_unified_memory_config(Unit *u) {
+        CGroupContext *c;
+
+        assert(u);
+
+        c = unit_get_cgroup_context(u);
+        assert(c);
+
+        return c->memory_min > 0 || unit_get_ancestor_memory_low(u) > 0 ||
+               c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX ||
+               c->memory_swap_max != CGROUP_LIMIT_MAX;
 }
 
 static void cgroup_apply_unified_memory_limit(Unit *u, const char *file, uint64_t v) {
@@ -1127,7 +1164,7 @@ static void cgroup_context_apply(
                 if (cg_all_unified() > 0) {
                         uint64_t max, swap_max = CGROUP_LIMIT_MAX;
 
-                        if (cgroup_context_has_unified_memory_config(c)) {
+                        if (unit_has_unified_memory_config(u)) {
                                 max = c->memory_max;
                                 swap_max = c->memory_swap_max;
                         } else {
@@ -1138,7 +1175,7 @@ static void cgroup_context_apply(
                         }
 
                         cgroup_apply_unified_memory_limit(u, "memory.min", c->memory_min);
-                        cgroup_apply_unified_memory_limit(u, "memory.low", c->memory_low);
+                        cgroup_apply_unified_memory_limit(u, "memory.low", unit_get_ancestor_memory_low(u));
                         cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high);
                         cgroup_apply_unified_memory_limit(u, "memory.max", max);
                         cgroup_apply_unified_memory_limit(u, "memory.swap.max", swap_max);
@@ -1149,7 +1186,7 @@ static void cgroup_context_apply(
                         char buf[DECIMAL_STR_MAX(uint64_t) + 1];
                         uint64_t val;
 
-                        if (cgroup_context_has_unified_memory_config(c)) {
+                        if (unit_has_unified_memory_config(u)) {
                                 val = c->memory_max;
                                 log_cgroup_compat(u, "Applying MemoryMax=%" PRIi64 " as MemoryLimit=", val);
                         } else
@@ -1323,8 +1360,13 @@ static bool unit_get_needs_bpf_firewall(Unit *u) {
         return false;
 }
 
-static CGroupMask cgroup_context_get_mask(CGroupContext *c) {
+static CGroupMask unit_get_cgroup_mask(Unit *u) {
         CGroupMask mask = 0;
+        CGroupContext *c;
+
+        assert(u);
+
+        c = unit_get_cgroup_context(u);
 
         /* Figure out which controllers we need, based on the cgroup context object */
 
@@ -1341,7 +1383,7 @@ static CGroupMask cgroup_context_get_mask(CGroupContext *c) {
 
         if (c->memory_accounting ||
             c->memory_limit != CGROUP_LIMIT_MAX ||
-            cgroup_context_has_unified_memory_config(c))
+            unit_has_unified_memory_config(u))
                 mask |= CGROUP_MASK_MEMORY;
 
         if (c->device_allow ||
@@ -1380,7 +1422,7 @@ CGroupMask unit_get_own_mask(Unit *u) {
         if (!c)
                 return 0;
 
-        return (cgroup_context_get_mask(c) | unit_get_bpf_mask(u) | unit_get_delegate_mask(u)) & ~unit_get_ancestor_disable_mask(u);
+        return (unit_get_cgroup_mask(u) | unit_get_bpf_mask(u) | unit_get_delegate_mask(u)) & ~unit_get_ancestor_disable_mask(u);
 }
 
 CGroupMask unit_get_delegate_mask(Unit *u) {
index 050b963579cb6b8ec88187d2eda5fff7a32452f8..4bbfa2c3a7f19fc210e030cc295a95f78a16b5a7 100644 (file)
@@ -98,12 +98,16 @@ struct CGroupContext {
         LIST_HEAD(CGroupIODeviceLimit, io_device_limits);
         LIST_HEAD(CGroupIODeviceLatency, io_device_latencies);
 
+        uint64_t default_memory_low;
         uint64_t memory_min;
         uint64_t memory_low;
         uint64_t memory_high;
         uint64_t memory_max;
         uint64_t memory_swap_max;
 
+        bool default_memory_low_set;
+        bool memory_low_set;
+
         LIST_HEAD(IPAddressAccessItem, ip_address_allow);
         LIST_HEAD(IPAddressAccessItem, ip_address_deny);
 
@@ -192,6 +196,8 @@ Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup);
 Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid);
 Unit* manager_get_unit_by_pid(Manager *m, pid_t pid);
 
+uint64_t unit_get_ancestor_memory_low(Unit *u);
+
 int unit_search_main_pid(Unit *u, pid_t *ret);
 int unit_watch_all_pids(Unit *u);
 
index 7a5e440e9cc0aebf2d35f6c5c661f328d79bdba1..5327bfb17d901045d77884e3251e806378084083 100644 (file)
@@ -347,6 +347,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
         SD_BUS_PROPERTY("BlockIOReadBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0),
         SD_BUS_PROPERTY("BlockIOWriteBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0),
         SD_BUS_PROPERTY("MemoryAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, memory_accounting), 0),
+        SD_BUS_PROPERTY("DefaultMemoryLow", "t", NULL, offsetof(CGroupContext, default_memory_low), 0),
         SD_BUS_PROPERTY("MemoryMin", "t", NULL, offsetof(CGroupContext, memory_min), 0),
         SD_BUS_PROPERTY("MemoryLow", "t", NULL, offsetof(CGroupContext, memory_low), 0),
         SD_BUS_PROPERTY("MemoryHigh", "t", NULL, offsetof(CGroupContext, memory_high), 0),
@@ -663,6 +664,9 @@ int bus_cgroup_set_property(
         if (streq(name, "MemoryLow"))
                 return bus_cgroup_set_memory(u, name, &c->memory_low, message, flags, error);
 
+        if (streq(name, "DefaultMemoryLow"))
+                return bus_cgroup_set_memory(u, name, &c->default_memory_low, message, flags, error);
+
         if (streq(name, "MemoryHigh"))
                 return bus_cgroup_set_memory(u, name, &c->memory_high, message, flags, error);
 
@@ -681,6 +685,9 @@ int bus_cgroup_set_property(
         if (streq(name, "MemoryLowScale"))
                 return bus_cgroup_set_memory_scale(u, name, &c->memory_low, message, flags, error);
 
+        if (streq(name, "DefaultMemoryLowScale"))
+                return bus_cgroup_set_memory_scale(u, name, &c->default_memory_low, message, flags, error);
+
         if (streq(name, "MemoryHighScale"))
                 return bus_cgroup_set_memory_scale(u, name, &c->memory_high, message, flags, error);
 
index 9cbce4f11fff52a2a25d59a9f5a25195637e3309..2a09d8e5887ba2f55f2cece3f7cff7b212f0eb3a 100644 (file)
@@ -172,6 +172,7 @@ $1.CPUQuota,                     config_parse_cpu_quota,             0,
 $1.CPUQuotaPeriodSec,            config_parse_sec_def_infinity,      0,                             offsetof($1, cgroup_context.cpu_quota_period_usec)
 $1.MemoryAccounting,             config_parse_bool,                  0,                             offsetof($1, cgroup_context.memory_accounting)
 $1.MemoryMin,                    config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
+$1.DefaultMemoryLow,             config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
 $1.MemoryLow,                    config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
 $1.MemoryHigh,                   config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
 $1.MemoryMax,                    config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
index 66e5c6d23174cced3c4370bd99192b6aeb25275d..faa694d90c1e3a9115d73db74a46f72b5d1f7f79 100644 (file)
@@ -3107,11 +3107,18 @@ int config_parse_memory_limit(
                 }
         }
 
-        if (streq(lvalue, "MemoryMin"))
+        if (streq(lvalue, "DefaultMemoryLow")) {
+                c->default_memory_low_set = true;
+                if (isempty(rvalue))
+                        c->default_memory_low = CGROUP_LIMIT_MIN;
+                else
+                        c->default_memory_low = bytes;
+        } else if (streq(lvalue, "MemoryMin"))
                 c->memory_min = bytes;
-        else if (streq(lvalue, "MemoryLow"))
+        else if (streq(lvalue, "MemoryLow")) {
                 c->memory_low = bytes;
-        else if (streq(lvalue, "MemoryHigh"))
+                c->memory_low_set = true;
+        } else if (streq(lvalue, "MemoryHigh"))
                 c->memory_high = bytes;
         else if (streq(lvalue, "MemoryMax"))
                 c->memory_max = bytes;
index 8d99955ca7856f6b334d107f3d7c09f9afe7de1c..cae526ba40931fe695f72f900a00d7428ef6c84a 100644 (file)
@@ -409,7 +409,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
                 return 1;
         }
 
-        if (STR_IN_SET(field, "MemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) {
+        if (STR_IN_SET(field, "MemoryMin", "DefaultMemoryLow", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "TasksMax")) {
 
                 if (isempty(eq) || streq(eq, "infinity")) {
                         r = sd_bus_message_append(m, "(sv)", field, "t", CGROUP_LIMIT_MAX);
index e31fcfbd5a645ad5e784c0d0876d8e391787c4f9..9c3ce2f712b2f42050564ef40ab24eb965b5ffd4 100644 (file)
@@ -794,7 +794,7 @@ static int bus_print_property(const char *name, const char *expected_value, sd_b
 
                         bus_print_property_value(name, expected_value, value, "[not set]");
 
-                else if ((STR_IN_SET(name, "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) ||
+                else if ((STR_IN_SET(name, "DefaultMemoryLow", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) ||
                          (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == (uint64_t) -1) ||
                          (startswith(name, "Limit") && u == (uint64_t) -1) ||
                          (startswith(name, "DefaultLimit") && u == (uint64_t) -1))
index 92bbcfd2765dec50e9b6c166befa597791a19c2b..c9edefdd8218fd2cd2f6e58a289d496f913624d2 100644 (file)
@@ -4129,6 +4129,8 @@ typedef struct UnitStatusInfo {
         uint64_t ip_ingress_bytes;
         uint64_t ip_egress_bytes;
 
+        uint64_t default_memory_low;
+
         LIST_HEAD(ExecStatusInfo, exec);
 } UnitStatusInfo;
 
@@ -5479,6 +5481,7 @@ static int show_one(
                 { "Where",                          "s",              NULL,           offsetof(UnitStatusInfo, where)                             },
                 { "What",                           "s",              NULL,           offsetof(UnitStatusInfo, what)                              },
                 { "MemoryCurrent",                  "t",              NULL,           offsetof(UnitStatusInfo, memory_current)                    },
+                { "DefaultMemoryLow",               "t",              NULL,           offsetof(UnitStatusInfo, default_memory_low)                },
                 { "MemoryMin",                      "t",              NULL,           offsetof(UnitStatusInfo, memory_min)                        },
                 { "MemoryLow",                      "t",              NULL,           offsetof(UnitStatusInfo, memory_low)                        },
                 { "MemoryHigh",                     "t",              NULL,           offsetof(UnitStatusInfo, memory_high)                       },
index ae970cf8a0dca1d53ddc60d9d3fe3ff2ec48be58..521985b927e4fffa82a4acd396f14fb766575a2f 100644 (file)
@@ -574,6 +574,12 @@ tests += [
           libshared],
          []],
 
+        [['src/test/test-cgroup-unit-default.c',
+          'src/test/test-helper.c'],
+         [libcore,
+          libshared],
+         []],
+
         [['src/test/test-cgroup-mask.c',
           'src/test/test-helper.c'],
          [libcore,
diff --git a/src/test/test-cgroup-unit-default.c b/src/test/test-cgroup-unit-default.c
new file mode 100644 (file)
index 0000000..7372f97
--- /dev/null
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <stdio.h>
+
+#include "cgroup.h"
+#include "manager.h"
+#include "rm-rf.h"
+#include "test-helper.h"
+#include "tests.h"
+#include "unit.h"
+
+static int test_default_memory_low(void) {
+        _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
+        _cleanup_(manager_freep) Manager *m = NULL;
+        Unit *root, *dml,
+             *dml_passthrough, *dml_passthrough_empty, *dml_passthrough_set_dml, *dml_passthrough_set_ml,
+             *dml_override, *dml_override_empty,
+             *dml_discard, *dml_discard_empty, *dml_discard_set_ml;
+        uint64_t dml_tree_default;
+        int r;
+
+        r = enter_cgroup_subroot();
+        if (r == -ENOMEDIUM)
+                return log_tests_skipped("cgroupfs not available");
+
+        assert_se(set_unit_path(get_testdata_dir()) >= 0);
+        assert_se(runtime_dir = setup_fake_runtime_dir());
+        r = manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m);
+        if (IN_SET(r, -EPERM, -EACCES)) {
+                log_error_errno(r, "manager_new: %m");
+                return log_tests_skipped("cannot create manager");
+        }
+
+        assert_se(r >= 0);
+        assert_se(manager_startup(m, NULL, NULL) >= 0);
+
+        /* dml.slice has DefaultMemoryLow=50. Beyond that, individual subhierarchies look like this:
+         *
+         * 1. dml-passthrough.slice sets MemoryLow=100. This should not affect its children, as only
+         *    DefaultMemoryLow is propagated, not MemoryLow. As such, all leaf services should end up with
+         *    memory.low as 50, inherited from dml.slice, *except* for dml-passthrough-set-ml.service, which
+         *    should have the value of 25, as it has MemoryLow explicitly set.
+         *
+         *                                                  ┌───────────┐
+         *                                                  │ dml.slice │
+         *                                                  └─────┬─────┘
+         *                                                  MemoryLow=100
+         *                                            ┌───────────┴───────────┐
+         *                                            │ dml-passthrough.slice │
+         *                                            └───────────┬───────────┘
+         *                    ┌───────────────────────────────────┼───────────────────────────────────┐
+         *             no new settings                   DefaultMemoryLow=15                     MemoryLow=25
+         *    ┌───────────────┴───────────────┐  ┌────────────────┴────────────────┐  ┌───────────────┴────────────────┐
+         *    │ dml-passthrough-empty.service │  │ dml-passthrough-set-dml.service │  │ dml-passthrough-set-ml.service │
+         *    └───────────────────────────────┘  └─────────────────────────────────┘  └────────────────────────────────┘
+         *
+         * 2. dml-override.slice sets DefaultMemoryLow=10. As such, dml-override-empty.service should also
+         *    end up with a memory.low of 10. dml-override.slice should still have a memory.low of 50.
+         *
+         *            ┌───────────┐
+         *            │ dml.slice │
+         *            └─────┬─────┘
+         *         DefaultMemoryLow=10
+         *        ┌─────────┴──────────┐
+         *        │ dml-override.slice │
+         *        └─────────┬──────────┘
+         *           no new settings
+         *    ┌─────────────┴──────────────┐
+         *    │ dml-override-empty.service │
+         *    └────────────────────────────┘
+         *
+         * 3. dml-discard.slice sets DefaultMemoryLow= with no rvalue. As such,
+         *    dml-discard-empty.service should end up with a value of 0.
+         *    dml-discard-explicit-ml.service sets MemoryLow=70, and as such should have that override the
+         *    reset DefaultMemoryLow value. dml-discard.slice should still have an eventual memory.low of 50.
+         *
+         *                           ┌───────────┐
+         *                           │ dml.slice │
+         *                           └─────┬─────┘
+         *                         DefaultMemoryLow=
+         *                       ┌─────────┴─────────┐
+         *                       │ dml-discard.slice │
+         *                       └─────────┬─────────┘
+         *                  ┌──────────────┴───────────────┐
+         *           no new settings                  MemoryLow=15
+         *    ┌─────────────┴─────────────┐  ┌─────────────┴──────────────┐
+         *    │ dml-discard-empty.service │  │ dml-discard-set-ml.service │
+         *    └───────────────────────────┘  └────────────────────────────┘
+         */
+        assert_se(manager_load_startable_unit_or_warn(m, "dml.slice", NULL, &dml) >= 0);
+
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough.slice", NULL, &dml_passthrough) >= 0);
+        assert_se(UNIT_DEREF(dml_passthrough->slice) == dml);
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-empty.service", NULL, &dml_passthrough_empty) >= 0);
+        assert_se(UNIT_DEREF(dml_passthrough_empty->slice) == dml_passthrough);
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-dml.service", NULL, &dml_passthrough_set_dml) >= 0);
+        assert_se(UNIT_DEREF(dml_passthrough_set_dml->slice) == dml_passthrough);
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-ml.service", NULL, &dml_passthrough_set_ml) >= 0);
+        assert_se(UNIT_DEREF(dml_passthrough_set_ml->slice) == dml_passthrough);
+
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-override.slice", NULL, &dml_override) >= 0);
+        assert_se(UNIT_DEREF(dml_override->slice) == dml);
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-override-empty.service", NULL, &dml_override_empty) >= 0);
+        assert_se(UNIT_DEREF(dml_override_empty->slice) == dml_override);
+
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-discard.slice", NULL, &dml_discard) >= 0);
+        assert_se(UNIT_DEREF(dml_discard->slice) == dml);
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-empty.service", NULL, &dml_discard_empty) >= 0);
+        assert_se(UNIT_DEREF(dml_discard_empty->slice) == dml_discard);
+        assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-set-ml.service", NULL, &dml_discard_set_ml) >= 0);
+        assert_se(UNIT_DEREF(dml_discard_set_ml->slice) == dml_discard);
+
+        root = UNIT_DEREF(dml->slice);
+        assert_se(!UNIT_ISSET(root->slice));
+
+        assert_se(unit_get_ancestor_memory_low(root) == CGROUP_LIMIT_MIN);
+
+        assert_se(unit_get_ancestor_memory_low(dml) == CGROUP_LIMIT_MIN);
+        dml_tree_default = unit_get_cgroup_context(dml)->default_memory_low;
+        assert_se(dml_tree_default == 50);
+
+        assert_se(unit_get_ancestor_memory_low(dml_passthrough) == 100);
+        assert_se(unit_get_ancestor_memory_low(dml_passthrough_empty) == dml_tree_default);
+        assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_dml) == 50);
+        assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_ml) == 25);
+
+        assert_se(unit_get_ancestor_memory_low(dml_override) == dml_tree_default);
+        assert_se(unit_get_ancestor_memory_low(dml_override_empty) == 10);
+
+        assert_se(unit_get_ancestor_memory_low(dml_discard) == dml_tree_default);
+        assert_se(unit_get_ancestor_memory_low(dml_discard_empty) == CGROUP_LIMIT_MIN);
+        assert_se(unit_get_ancestor_memory_low(dml_discard_set_ml) == 15);
+
+        return 0;
+}
+
+int main(int argc, char* argv[]) {
+        int rc = EXIT_SUCCESS;
+
+        test_setup_logging(LOG_DEBUG);
+
+        TEST_REQ_RUNNING_SYSTEMD(rc = test_default_memory_low());
+
+        return rc;
+}
diff --git a/test/dml-discard-empty.service b/test/dml-discard-empty.service
new file mode 100644 (file)
index 0000000..75228f6
--- /dev/null
@@ -0,0 +1,7 @@
+[Unit]
+Description=DML discard empty service
+
+[Service]
+Slice=dml-discard.slice
+Type=oneshot
+ExecStart=/bin/true
diff --git a/test/dml-discard-set-ml.service b/test/dml-discard-set-ml.service
new file mode 100644 (file)
index 0000000..591c992
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description=DML discard set ml service
+
+[Service]
+Slice=dml-discard.slice
+Type=oneshot
+ExecStart=/bin/true
+MemoryLow=15
diff --git a/test/dml-discard.slice b/test/dml-discard.slice
new file mode 100644 (file)
index 0000000..e26d868
--- /dev/null
@@ -0,0 +1,5 @@
+[Unit]
+Description=DML discard slice
+
+[Slice]
+DefaultMemoryLow=
diff --git a/test/dml-override-empty.service b/test/dml-override-empty.service
new file mode 100644 (file)
index 0000000..142c987
--- /dev/null
@@ -0,0 +1,7 @@
+[Unit]
+Description=DML override empty service
+
+[Service]
+Slice=dml-override.slice
+Type=oneshot
+ExecStart=/bin/true
diff --git a/test/dml-override.slice b/test/dml-override.slice
new file mode 100644 (file)
index 0000000..feb6773
--- /dev/null
@@ -0,0 +1,5 @@
+[Unit]
+Description=DML override slice
+
+[Slice]
+DefaultMemoryLow=10
diff --git a/test/dml-passthrough-empty.service b/test/dml-passthrough-empty.service
new file mode 100644 (file)
index 0000000..34832de
--- /dev/null
@@ -0,0 +1,7 @@
+[Unit]
+Description=DML passthrough empty service
+
+[Service]
+Slice=dml-passthrough.slice
+Type=oneshot
+ExecStart=/bin/true
diff --git a/test/dml-passthrough-set-dml.service b/test/dml-passthrough-set-dml.service
new file mode 100644 (file)
index 0000000..5bdf4ed
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description=DML passthrough set DML service
+
+[Service]
+Slice=dml-passthrough.slice
+Type=oneshot
+ExecStart=/bin/true
+DefaultMemoryLow=15
diff --git a/test/dml-passthrough-set-ml.service b/test/dml-passthrough-set-ml.service
new file mode 100644 (file)
index 0000000..2abd591
--- /dev/null
@@ -0,0 +1,8 @@
+[Unit]
+Description=DML passthrough set ML service
+
+[Service]
+Slice=dml-passthrough.slice
+Type=oneshot
+ExecStart=/bin/true
+MemoryLow=25
diff --git a/test/dml-passthrough.slice b/test/dml-passthrough.slice
new file mode 100644 (file)
index 0000000..1b1a848
--- /dev/null
@@ -0,0 +1,5 @@
+[Unit]
+Description=DML passthrough slice
+
+[Slice]
+MemoryLow=100
diff --git a/test/dml.slice b/test/dml.slice
new file mode 100644 (file)
index 0000000..84e333e
--- /dev/null
@@ -0,0 +1,5 @@
+[Unit]
+Description=DML slice
+
+[Slice]
+DefaultMemoryLow=50
index 7255c79dcb8ccc92f1be7ae21a842338ffea2f8a..17d0f3cddddeea790c5e14e3f37041a20993459d 100644 (file)
@@ -7,6 +7,16 @@ test_data_files = '''
         c.service
         d.service
         daughter.service
+        dml.slice
+        dml-passthrough.slice
+        dml-passthrough-empty.service
+        dml-passthrough-set-dml.service
+        dml-passthrough-set-ml.service
+        dml-override.slice
+        dml-override-empty.service
+        dml-discard.slice
+        dml-discard-empty.service
+        dml-discard-set-ml.service
         e.service
         end.service
         f.service