From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail.openvz.org (unknown [69.168.225.77]) by lore.virtuozzo.com (Postfix) with ESMTPS id C67338069D for ; Fri, 25 Sep 2026 16:55:26 +0000 (UTC) Received: from mail.openvz.org (localhost [127.0.0.1]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 68PGsBUa027477; Fri, 25 Sep 2026 19:54:11 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 68PGsBUa027477 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="orwFF10Q" Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 68PGs1gI027430 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Fri, 25 Sep 2026 19:54:01 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 68PGs1gI027430 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-4887efa4486so669439f8f.1 for ; Fri, 25 Sep 2026 09:54:01 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvBzChn4WSFpRaUYWnsdfapUh7XZ4vshlE6yGjwZQi+gEtSxS7uhi8vXNt6GZjqfzOZ2GFLzt3Q==@openvz.org X-Gm-Message-State: AFuF++kpnUMO72BvAFMjiK/VOctmB+8bd30Gu0IHjJlmfh5L7h1hZsZD dJd+CDj2crP1PRUGFwPL28xHT3tslwHJCliSpZmQqXy6qfkJG6QURKef4fBLBoFziuBeWPKSPY4 1dioZJxB9MsoJPmSHnouWz5Ph5+2Z7VddmkWVz/3GwJrGFO2YMITTKQ== X-Gm-Gg: AYBFou1ssoUcYRra1OulmTpBaWLpnzUfNicusIDB+XIv4cTMihrU7jap3IRDwrzJMkk 0O5sEbWbV9GakRGNhEvdbds6HOajdGFzF/302HyJfu4/6xUGng7HQ2uL0CLxdOoLC1uurQSkKVa L6SuCY+Re05RVwmXeQ6u8GaJCLNtGt/zjImfI3P9Gs+GZji02oLiEm1I3BBMf54X8dLwn1CObPE PPEBHBuhLcEuwlW/dFkfToPvvHTYoP57zDEBXm+bdhuBgr5Dl2Z+iXyEZXW+A37ZtpHqRCgOSpv /ZUQBlk+TBtKcbVGBjS/QhRQ+kEqx5H7R4NQAlMiegLFMkCxDQ8ZZHZjKOiutxQOUVDljFo0HU2 VfUXQDLlA9B9Yv36c X-Received: by 2002:a05:6000:1a85:b0:487:ee4:b5e4 with SMTP id ffacd0b85a97d-488716b6cd7mr12367012f8f.8.1790355241341; Fri, 25 Sep 2026 09:54:01 -0700 (PDT) X-Received: by 2002:a05:6000:1a85:b0:487:ee4:b5e4 with SMTP id ffacd0b85a97d-488716b6cd7mr12366968f8f.8.1790355240732; Fri, 25 Sep 2026 09:54:00 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1790355240; cv=none; d=google.com; s=arc-20260327; b=NZ49MPFOKMqbwjb9EuYAgRqpItE97E4kMUkyBbFL8NOu4/FObjkwXrxgW3SlcD/TdB 4H9XnGdj5IBl/mJ4BU/87DiQCyINfyLHFyWL1IOeDBKgZvCEZB9KjMxhZc+QrsGvBuS9 304oc3lzlAUEBHXInXshyHS/nRmR/L9qpVzQ8tRV2Ey9nWQhPvusCaE1YAguZg+zpLSo K7tugV4rXk9sewrMurGfN/k3W4ng4+oJFMv65jIY07YkD4AZ8JdiJ7lrWWq1uaR15gLH e2GzVJXOUJmgYlHgzQv5wiWzY/MR/AJv6OY5tJkbZ8egNRFIwpESHNXEwRGU11osJHR6 FPZA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:dkim-signature; bh=yILkgnKa8L0rKXGZoAyrhPc/SYQHfo5GhN9FNsChR+w=; fh=zK/v/gHQ7GavWYhLJT8+SASJ7h8osfxqNHUzmzGkT+o=; b=G+mTLck9D+jQQ0yuBtkDhjf8pDZyzkj8LbDjtqbsE2GcYpXgUW8pa8iKYcKTklbfcA WRDhtV3gamdD0dO6Gre+W7/YNosrLuoTChVpQ0d/iLlB/7Cs5616Iqu3xr8vvAkcO3KW AWdUSIYxuYR3DqHl4Ilve7y/8xRp8myj+54YTPv2tPOf7MFvUSdCulrP0y7mM0lXB1XT KASfG+ktx+nnOwkzzV1JDrPrWtiLVkuRjYyOvnpXJ7RC28KM2K+rCSOaaQMT5qG3hu/3 NRA74EvL2BZq48yUujmrMcwMYcY/j7IlmOlMswPKFHhzwsP6yDc5soVMr5fd7+i3AAnk aQcA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=orwFF10Q; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com Received: from relay.virtuozzo.com (relay.virtuozzo.com. [130.117.225.111]) by mx.google.com with ESMTPS id ffacd0b85a97d-4887a361cc9si5827254f8f.47.2026.09.25.09.54.00 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 25 Sep 2026 09:54:00 -0700 (PDT) Received-SPF: pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) client-ip=130.117.225.111; Authentication-Results: mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=orwFF10Q; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=virtuozzo.com; s=relay; h=Message-Id:Content-Type:MIME-Version:Subject:Date :From; bh=yILkgnKa8L0rKXGZoAyrhPc/SYQHfo5GhN9FNsChR+w=; b=orwFF10Q0UPHms/NoGB aXWouHjeHVt5emaaTtUY933SHA+5O+4el/l+ni1Vj1EsGN15Yyf4xwFL+r05tQiQsDYvKaV69pPE9 HQNWBn3viDrnrY8dIlwvRmQzzLNjC/3XYHYPYwDGJDKUacxGJ2+UehBaTaQ8tAdufgABdGN+HBYTa EIEP9koz3lt0uAU99lTuPb9fWCF2T4gNaoT5YskLve4DjekvAn5CHHjYqNkOVOrzzap11Z3KgU0sL MfzqGvdskQ4ASCj2TuuNn6crppsLVU2eCrfpBKUKBX+cv0zmTabl2+DzGZv2h4YrvHz6+6ch1iZj6 af7PcfydCqbYSMg==; Received: from ch-demo-asa.virtuozzo.com ([130.117.225.8] helo=f0.sw.ru) by relay.virtuozzo.com with esmtp (Exim 4.96) (envelope-from ) id 1xA986-00CFkL-2j; Fri, 25 Sep 2026 18:53:35 +0200 From: Konstantin Khorenko Date: Fri, 25 Sep 2026 18:53:34 +0200 MIME-Version: 1.0 Message-Id: <20260925-b4-kh-ve_fail-v10-13-77a6b6ea4205@virtuozzo.com> References: <20260925-b4-kh-ve_fail-v10-0-77a6b6ea4205@virtuozzo.com> In-Reply-To: <20260925-b4-kh-ve_fail-v10-0-77a6b6ea4205@virtuozzo.com> To: Vasileios Almpanis , Pavel Tikhomirov , cover.1789491989.git.vladimir.riabchun@virtuozzo.com, Konstantin Khorenko , devel@openvz.org X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=20191; i=khorenko@virtuozzo.com; h=from:subject:message-id; bh=z2HOUXdEqPajS8rWfupxORYFjIG95Exto5nvujQeO+c=; b=owEBbQKS/ZANAwAKAVGWCkf5YVwqAcsmYgBqtqcPQBRaBR3rA8VUQrOL3tzaqXtZFeJvStCSo v/5lRKKZoCJAjMEAAEKAB0WIQRWD23IPcXT0GO9CCxRlgpH+WFcKgUCaranDwAKCRBRlgpH+WFc KqkLD/4uRhu+z6rvSDXLINFTT7Aa5RFXd1IaapUrgdieyAMbGDuJ9opD74HcIBBScUXATLSfOAj aHNg85Y1kn66suq8pZfB/R24n67dY2RP456WFtpQ+sVMWjQ/Gdsd6aP2wHc7iH6sz1X3TkJYLLz BljTN9PIrMTkEG+4K2Jsd5jGM6sTv6jDs/43Jjcgqyoc/bAr6cRkKZvuk9Tcr3Y/PGBi/aEiiLz z/CdMbRjsURmQm/qT2QPwz3XGnRIqv3krFiGOAKhp8snuwOk6iVa4VF1zZkFeqD8xX0sTTcyXze E6QpGRHAE/XwIgBIlEw0RNgZ74UNmwaxdrhzGAqApN8I/YPmbEH3/5E5aSVZEdq/tpD+pECjUzD 88SUiX3jSho7l9HKzbY6blny6fmXUCMUkwSv0Zlf77k4BYF3TNJuhy52fO+VqrFaU7AQWlbdCKs Dojzc8Mg9BMUjf2A/GGhKWDOn652wx16aIh02MLKQ6uhr/HlQ0Xn46URxHLPaotkss+qozRhtwJ GG1XL5pLZedKwoN4GGG5t/55SyEq2PhDHN76403/Rd2EahRcUmmUbTWthFJmWV3iop1GtayNON5 XPK0SXwbLE+ubPWzqJJpbU84ZeO0u/131td7iDXv2Vbbx3tqEZqCBSs/LvFI4Utz+rfFDYjgosa tOncc9eioTGqVWQ== X-Developer-Key: i=khorenko@virtuozzo.com; a=openpgp; fpr=560F6DC83DC5D3D063BD082C51960A47F9615C2A X-OZ-Fwd: true Subject: [Devel] [PATCH hci-8.1 v10 13/13] selftests/ve: add mount accounting selftest X-BeenThere: devel@openvz.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: OpenVZ development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: devel-bounces@openvz.org Errors-To: devel-bounces@openvz.org From: Vladimir Riabchun There are 6 test cases, covered in the new test: 1. Simple mount accouting correctness, just mount/umount. 2. Verification of correct limit hits and changes, including negative values. 3. Partial mounts test, when mount limit is hit in the middle of creation. 4. Test that enabled pseudosuper allows overuse. 5. Test that pseudosuper doesn't affect mount accounting. 6. Failcount feature verification. https://virtuozzo.atlassian.net/browse/VSTOR-135520 Feature: per-ve failcounters Signed-off-by: Vladimir Riabchun Signed-off-by: Konstantin Khorenko --- tools/testing/selftests/ve/.gitignore | 1 + tools/testing/selftests/ve/Makefile | 1 + .../selftests/ve/ve_mount_accounting_test.c | 554 +++++++++++++++++++++ tools/testing/selftests/ve/ve_selftest.h | 21 + 4 files changed, 577 insertions(+) diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore index afa4c568c2c9b..3df4d05888dce 100644 --- a/tools/testing/selftests/ve/.gitignore +++ b/tools/testing/selftests/ve/.gitignore @@ -1,2 +1,3 @@ ve_ns_owner_test ve_perms_test +ve_mount_accounting_test diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile index ec40cbc7b3a1b..c6efe7c4b4fba 100644 --- a/tools/testing/selftests/ve/Makefile +++ b/tools/testing/selftests/ve/Makefile @@ -4,5 +4,6 @@ CFLAGS += -g -Wall -O2 TEST_GEN_PROGS += ve_ns_owner_test TEST_GEN_PROGS += ve_perms_test +TEST_GEN_PROGS += ve_mount_accounting_test include ../lib.mk diff --git a/tools/testing/selftests/ve/ve_mount_accounting_test.c b/tools/testing/selftests/ve/ve_mount_accounting_test.c new file mode 100644 index 0000000000000..e2a882137e11d --- /dev/null +++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ve_mount_accounting selftests + * + * Tests to check the correctness of mount accounting. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" +#include "ve_selftest.h" + +#define TMP_DIR "/ve-mnt-tmp/" +#define VE_MOUNTS_MAX 128 + +/* + * These values take a while to calculate, the first test will + * do this and store the values for those who come after. + */ +int *need_avail; + +FIXTURE(ve_mnt_acc) +{ + int cgv2_fd; + int ctid; + struct namespace_ctx ns_ctx; + int orig_have_mnt; +}; + +/* + * free_vfsmnt is called via call_rcu, so we need to ensure + * that all pending call_rcu tasks are finished. + * However, one barrier is not enough: + * mntput_no_expire + * init_task_work(&mnt->mnt_rcu, __cleanup_mnt); + * task_work_add(task, &mnt->mnt_rcu, TWA_RESUME) + * OR + * schedule_delayed_work(&delayed_mntput_work, 1) + * # Both later call cleanup_mnt + * cleanup_mnt + * call_rcu(&mnt->mnt_rcu, delayed_free_vfsmnt); + * + * We can't enforce workqueue flush from userspace, so instead + * read several times until the value is the one we expect. + */ +static int wait_on_mnt_avail(FIXTURE_DATA(ve_mnt_acc) * self, int expected) +{ +#define NSEC_PER_SEC (1000ULL * 1000 * 1000) +#define WAIT_TIMEOUT_NS (4096ULL * 1000 * 1000) + char path_avail[64]; + int mnt_avail_nr = 0; + unsigned long long delay_ns = 1000000; + struct timespec delay; + + snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid); + + while (delay_ns <= WAIT_TIMEOUT_NS) { + if (read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr)) { + fprintf(stderr, "Failed to read from %s\n", path_avail); + return 1; + } + if (mnt_avail_nr == expected) + return 0; + + do_rcu_barrier(); + delay.tv_sec = delay_ns / NSEC_PER_SEC; + delay.tv_nsec = delay_ns % NSEC_PER_SEC; + if (nanosleep(&delay, NULL)) { + fprintf(stderr, "nanosleep failed: %s\n", strerror(errno)); + return 1; + } + delay_ns *= 2; + } + fprintf(stderr, "Didn't get expected value at %s\n", path_avail); + fprintf(stderr, "Expected %d, last read %d\n", expected, mnt_avail_nr); + return -1; +} + +static int set_pseudosuper(FIXTURE_DATA(ve_mnt_acc) * self, int value) +{ + char path[64]; + + snprintf(path, sizeof(path), "%d/ve.pseudosuper", self->ctid); + return write_u64_at(self->cgv2_fd, path, value); +} + +static int _create_mount(void *id_ptr) +{ + char path[PATH_MAX]; + int id = *(int *)id_ptr, ret; + + snprintf(path, sizeof(path), TMP_DIR "%d", id); + + if (mkdir(path, 0755) < 0) { + fprintf(stderr, "Failed to create directory %s: %s\n", path, strerror(errno)); + return -1; + } + ret = mount("tmpfs", path, "tmpfs", 0, "size=1M"); + if (!ret) + return 0; + fprintf(stderr, "Failed to mount tmpfs to %s: %s\n", path, strerror(errno)); + + rmdir(path); + return -1; +} + +/* Create a mount and wait until mount counter is valid */ +static int create_mount(FIXTURE_DATA(ve_mnt_acc) * self, int id) +{ + int ret, avail_before; + char path[64]; + + snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid); + + if (read_s32_at(self->cgv2_fd, path, &avail_before)) { + fprintf(stderr, "Failed to read mnt_avail_nr in create_mount\n"); + return 1; + } + + ret = run_in_ve(self->cgv2_fd, self->ctid, &self->ns_ctx, + CLONE_NEWVE | CLONE_NEWNS, _create_mount, &id); + + if (ret) + wait_on_mnt_avail(self, avail_before); + else + ret = wait_on_mnt_avail(self, avail_before - 1); + return ret; +} + +static int _destroy_mount(void *id_ptr) +{ + char path[PATH_MAX]; + struct stat st; + int id = *(int *)id_ptr; + + snprintf(path, sizeof(path), TMP_DIR "%d", id); + + if (stat(path, &st)) + return 1; + if (umount(path)) { + fprintf(stderr, "failed to umount directory %s: %s\n", path, strerror(errno)); + return -1; + } + if (rmdir(path)) { + fprintf(stderr, "failed to remove directory %s: %s\n", path, strerror(errno)); + return -1; + } + return 0; +} + +/* Do umount and ensure that mnt_avail_nr is valid on exit(+1) */ +static int destroy_mount(FIXTURE_DATA(ve_mnt_acc) * self, int id) +{ + int ret, avail_before; + char path[64]; + + snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid); + + if (read_s32_at(self->cgv2_fd, path, &avail_before)) { + fprintf(stderr, "Failed to read mnt_avail_nr in create_mount\n"); + return 1; + } + + ret = run_in_ve(self->cgv2_fd, self->ctid, &self->ns_ctx, + CLONE_NEWVE | CLONE_NEWNS, _destroy_mount, &id); + + /* Cost must be exactly 1 */ + if (!ret) + ret = wait_on_mnt_avail(self, avail_before + 1); + + return ret; +} + +#define MAX_MNT_ID 32 + +static int get_free_mnt_id(void) +{ + int i; + struct stat st; + char path[PATH_MAX]; + + for (i = 0; i < MAX_MNT_ID; i++) { + snprintf(path, sizeof(path), TMP_DIR "%d", i); + if (stat(path, &st)) + return i; + } + return -1; +} + +/* Returns 0 if mount failed OR mount cost is not 1 */ +static int try_mount(FIXTURE_DATA(ve_mnt_acc) * self) +{ + int mnt_id = get_free_mnt_id(); + + if (mnt_id < 0 || create_mount(self, mnt_id)) + return 0; + + return destroy_mount(self, mnt_id) == 0; +} + +/* + * Current kernel code first creates many mounts with ve_owner != ve0, + * so this VE needs more mounts than would be really consumed after commit_tree. + */ +static int get_required_avail_mnts(FIXTURE_DATA(ve_mnt_acc) * self) +{ + int l = 0, r = VE_MOUNTS_MAX + 1, mid; + char path_max_nr[64]; + + snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid); + + while (r - l > 1) { + mid = (l + r) / 2; + if (write_u64_at(self->cgv2_fd, path_max_nr, mid + self->orig_have_mnt)) + return -1; + + if (try_mount(self)) + r = mid; + else + l = mid; + } + if (r == VE_MOUNTS_MAX + 1) { + fprintf(stderr, "%s: %d avail mounts is not enough\n", __func__, r); + return -1; + } + if (write_u64_at(self->cgv2_fd, path_max_nr, VE_MOUNTS_MAX)) + return -1; + return r; +} + +/* Expect mount success and return new avail value */ +static int mount_and_get_avail(struct __test_metadata *_metadata, + FIXTURE_DATA(ve_mnt_acc) * self, int mnt_id) +{ + char path_avail[64]; + int mnt_avail_nr; + + snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid); + + ASSERT_EQ(create_mount(self, mnt_id), 0); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0); + return mnt_avail_nr; +} + +/* Expect mount failure and ensure intact avail number */ +static int check_mount_fails(struct __test_metadata *_metadata, + FIXTURE_DATA(ve_mnt_acc) * self, + int mnt_id, int avail_count) +{ + char path_avail[64]; + int mnt_avail_nr; + + snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid); + + if (read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr)) + return 1; + if (mnt_avail_nr != avail_count) { + fprintf(stderr, "%s: mnt_avail(%d) != avail_count(%d) before mount\n", + __func__, mnt_avail_nr, avail_count); + return -1; + } + + if (create_mount(self, mnt_id) == 0) { + fprintf(stderr, "%s: successful mount\n", __func__); + return -1; + } + + if (read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr)) + return 1; + if (mnt_avail_nr != avail_count) { + fprintf(stderr, "%s: mnt_avail(%d) != avail_count(%d) after mount\n", + __func__, mnt_avail_nr, avail_count); + return -1; + } + return 0; +} + +FIXTURE_SETUP(ve_mnt_acc) +{ + int initial_mnt_avail_nr; + char path[64]; + + self->cgv2_fd = mount_cg2_fd(); + ASSERT_GE(self->cgv2_fd, 0); + mkdir(TMP_DIR, 0755); + + ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control", + VE_CONTROLLERS), 0); + + self->ctid = make_ve(self->cgv2_fd, CTID_MIN); + ASSERT_GE(self->ctid, 0); + + snprintf(path, sizeof(path), "%d/ve.mnt_max_nr", self->ctid); + ASSERT_EQ(write_u64_at(self->cgv2_fd, path, VE_MOUNTS_MAX), 0); + + /* + * The new ve cgroup has not been entered by anything yet, so its + * mnt_avail_nr counter should be VE_MOUNTS_MAX. + */ + snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0); + ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX); + + ASSERT_EQ(prepare_namespaces(&self->ns_ctx, CLONE_NEWNS | CLONE_NEWVE, + self->cgv2_fd, self->ctid), 0); + + ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0); + self->orig_have_mnt = VE_MOUNTS_MAX - initial_mnt_avail_nr; + + /* Calculate once - it is time-consuming */ + if (*need_avail == -1) + *need_avail = get_required_avail_mnts(self); + + ASSERT_GE(*need_avail, 1); + /* try_mount ensures cost is 1 */ + ASSERT_EQ(try_mount(self), 1); +}; + +FIXTURE_TEARDOWN(ve_mnt_acc) +{ + for (int i = 0; i < MAX_MNT_ID; i++) + _destroy_mount((void *)&i); + + close_namespace_fds(&self->ns_ctx); + destroy_ve(self->cgv2_fd, self->ctid); + close(self->cgv2_fd); + rmdir(TMP_DIR); +} + +/* Simple test to check mount/umount accounting correctness */ +TEST_F(ve_mnt_acc, mount_umount) +{ + int original_mnt_avail, mnt_avail_nr; + char path[64]; + + snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid); + + ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &original_mnt_avail), 0); + + ASSERT_LT(mount_and_get_avail(_metadata, self, 0), + original_mnt_avail); + + ASSERT_EQ(destroy_mount(self, 0), 0); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &mnt_avail_nr), 0); + ASSERT_EQ(mnt_avail_nr, original_mnt_avail); +} + +/* Test mount limit hits */ +TEST_F(ve_mnt_acc, hit_limits) +{ + int mnt_avail_nr; + char path_avail[64], path_max_nr[64]; + + snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid); + snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid); + + /* Step 1: reduce number of available mounts to *need_avail */ + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, + self->orig_have_mnt + 1 * (*need_avail)), 0); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0); + ASSERT_EQ(mnt_avail_nr, 1 * *need_avail); + + /* Step 2: do one mount, no mounts should be available */ + ASSERT_EQ(mount_and_get_avail(_metadata, self, 0), *need_avail - 1); + + /* Step 3: check that one more mount fails */ + ASSERT_EQ(check_mount_fails(_metadata, self, 1, (*need_avail) - 1), 0); + + /* Step 4: increase by 1 and win now */ + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, + self->orig_have_mnt + 1 + *need_avail), 0); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0); + ASSERT_EQ(mnt_avail_nr, *need_avail); + + ASSERT_EQ(mount_and_get_avail(_metadata, self, 1), *need_avail - 1); + + /* Step 5: reduce mnt_max_nr so we have more mounts than allowed */ + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, + self->orig_have_mnt + 1), 0); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0); + ASSERT_EQ(mnt_avail_nr, -1); + + /* Step 6: try to do mount when avail < 0, ensure number is intact */ + ASSERT_EQ(check_mount_fails(_metadata, self, 2, -1), 0); + + /* Step 7: remove one mount, check avail value update, mount should fail */ + ASSERT_EQ(destroy_mount(self, 0), 0); + ASSERT_EQ(check_mount_fails(_metadata, self, 2, 0), 0); + + /* Step 8: remove one more mount and check that new mount succeeds */ + ASSERT_EQ(destroy_mount(self, 1), 0); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0); + ASSERT_EQ(mnt_avail_nr, 1); + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, self->orig_have_mnt + *need_avail), 0); + ASSERT_EQ(mount_and_get_avail(_metadata, self, 2), *need_avail - 1); + + ASSERT_EQ(destroy_mount(self, 2), 0); +} + +/* + * Mount propagation makes one mount cost more. + * This test checks that if we run out of mounts in the middle of creating + * a new one, everything is restored smoothly and nothing leaks. + */ +TEST_F(ve_mnt_acc, partial_mounts) +{ + char path_avail[64], path_max_nr[64]; + int i; + + snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid); + snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid); + + if (*need_avail == 1) + SKIP(return, "mount cost is 1, no partial mounts possible"); + + for (i = 0; i < *need_avail; i++) { + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, self->orig_have_mnt + i), 0); + ASSERT_EQ(check_mount_fails(_metadata, self, 0, i), 0); + } +} + +/* Test that pseudosuper allows negative avail with correct accounting. */ +TEST_F(ve_mnt_acc, pseudosuper_allows_overuse) +{ + char path_max_nr[64]; + + snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid); + + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, self->orig_have_mnt + *need_avail), 0); + ASSERT_EQ(set_pseudosuper(self, 1), 0); + + ASSERT_EQ(mount_and_get_avail(_metadata, self, 0), *need_avail - 1); + ASSERT_EQ(mount_and_get_avail(_metadata, self, 1), *need_avail - 2); + + /* Go below zero */ + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, self->orig_have_mnt), 0); + ASSERT_EQ(mount_and_get_avail(_metadata, self, 2), -3); + + ASSERT_EQ(set_pseudosuper(self, 0), 0); + + /* Below zero should fail */ + ASSERT_EQ(check_mount_fails(_metadata, self, 3, -3), 0); + /* Now need 2 more to add a new mount */ + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, + self->orig_have_mnt + 3 + *need_avail - 2), 0); + ASSERT_EQ(check_mount_fails(_metadata, self, 3, *need_avail - 2), 0); + + ASSERT_EQ(destroy_mount(self, 0), 0); + ASSERT_EQ(check_mount_fails(_metadata, self, 3, *need_avail - 1), 0); + ASSERT_EQ(destroy_mount(self, 2), 0); + + ASSERT_EQ(try_mount(self), 1); +} + +/* Test that pseudosuper doesn't disable accounting. */ +TEST_F(ve_mnt_acc, pseudosuper_continues_accounting) +{ + int orig_mnt_avail, mnt_avail_nr; + char path_avail[64]; + + snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0); + + ASSERT_EQ(set_pseudosuper(self, 0), 0); + + /* mnt 0 - mounted without pseudosuper, umounted with it. */ + ASSERT_EQ(mount_and_get_avail(_metadata, self, 0), orig_mnt_avail - 1); + ASSERT_EQ(set_pseudosuper(self, 1), 0); + + /* Cost is the same when mount/umount happen under pseudosuper. */ + ASSERT_EQ(try_mount(self), 1); + + /* mnt 1 - mounted with pseudosuper, umounted without it. */ + ASSERT_EQ(mount_and_get_avail(_metadata, self, 1), + orig_mnt_avail - 2); + + ASSERT_EQ(destroy_mount(self, 0), 0); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0); + ASSERT_EQ(mnt_avail_nr, orig_mnt_avail - 1); + + ASSERT_EQ(set_pseudosuper(self, 0), 0); + + ASSERT_EQ(destroy_mount(self, 1), 0); + ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0); + ASSERT_EQ(mnt_avail_nr, orig_mnt_avail); +} + +/* Test failcount feature */ +TEST_F(ve_mnt_acc, failcount) +{ + char path_fc[64], failcount_str[512], path_max_nr[64]; + + snprintf(path_fc, sizeof(path_fc), "%d/ve.failcount", self->ctid); + snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid); + + ASSERT_GE(read_file_at(self->cgv2_fd, path_fc, + failcount_str, sizeof(failcount_str)), 0); + ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL); + + /* Check successful mount doesn't affect failcount */ + mount_and_get_avail(_metadata, self, 0); + ASSERT_GE(read_file_at(self->cgv2_fd, path_fc, + failcount_str, sizeof(failcount_str)), 0); + ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL); + ASSERT_EQ(destroy_mount(self, 0), 0); + + /* Check failcount update when mount fails */ + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0); + ASSERT_LT(create_mount(self, 1), 0); + ASSERT_GE(read_file_at(self->cgv2_fd, path_fc, + failcount_str, sizeof(failcount_str)), 0); + ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL); + + /* Check failcount flush */ + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_fc, 0), 0); + ASSERT_GE(read_file_at(self->cgv2_fd, path_fc, + failcount_str, sizeof(failcount_str)), 0); + ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL); + + /* Check failcount update when mount fails again */ + ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0); + ASSERT_LT(create_mount(self, 1), 0); + ASSERT_GE(read_file_at(self->cgv2_fd, path_fc, + failcount_str, sizeof(failcount_str)), 0); + ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL); +} + +int main(int argc, char **argv) +{ + need_avail = mmap(NULL, sizeof(int), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + + if (need_avail == MAP_FAILED) { + fprintf(stderr, "Failed to mmap shared region: %s\n", strerror(errno)); + return 1; + } + *need_avail = -1; + + return test_harness_run(argc, argv); +} diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h index 7b45f91c1d561..1fc3f8dcac180 100644 --- a/tools/testing/selftests/ve/ve_selftest.h +++ b/tools/testing/selftests/ve/ve_selftest.h @@ -416,4 +416,25 @@ static inline int is_mounted(const char *path) return errno == EXDEV ? 1 : -1; } +static inline int do_rcu_barrier(void) +{ + int fd; + + errno = 0; + fd = open("/sys/module/rcutree/parameters/do_rcu_barrier", O_WRONLY); + + if (fd < 0) { + fprintf(stderr, "Failed to open do_rcu_barrier: %s\n", strerror(errno)); + return -1; + } + errno = 0; + if (write(fd, "1", 2) != 2) { + fprintf(stderr, "Failed to issue rcu_barrier: %s\n", strerror(errno)); + close(fd); + return -1; + } + close(fd); + return 0; +} + #endif /* __SELFTESTS_VE_VE_SELFTEST_H */ -- 2.43.0 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel