xref: /freebsd/sys/contrib/openzfs/tests/zfs-tests/tests/functional/dedup/dedup_quota.ksh (revision 071ab5a1f3cbfd29c8fbec27f7e619418adaf074)
1#!/bin/ksh -p
2# SPDX-License-Identifier: CDDL-1.0
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or https://opensource.org/licenses/CDDL-1.0.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22
23#
24# Copyright (c) 2023, Klara Inc.
25#
26
27# DESCRIPTION:
28#	Verify that new entries are not added to the DDT when dedup_table_quota has
29#	been exceeded.
30#
31# STRATEGY:
32#	1. Create a pool with dedup=on
33#	2. Set threshold for on-disk DDT via dedup_table_quota
34#	3. Verify the threshold is exceeded after zpool sync
35#	4. Verify no new entries are added after subsequent sync's
36#	5. Remove all but one entry from DDT
37#	6. Verify new entries are added to DDT
38#
39
40. $STF_SUITE/include/libtest.shlib
41. $STF_SUITE/tests/functional/events/events_common.kshlib
42
43verify_runnable "both"
44
45log_assert "DDT quota is enforced"
46
47MOUNTDIR="$TEST_BASE_DIR/dedup_mount"
48FILEPATH="$MOUNTDIR/dedup_file"
49VDEV_GENERAL="$TEST_BASE_DIR/vdevfile.general.$$"
50VDEV_DEDUP="$TEST_BASE_DIR/vdevfile.dedup.$$"
51POOL="dedup_pool"
52
53# we set the dedup log txg interval to 1, to get a log flush every txg,
54# effectively disabling the log. without this it's hard to predict when and
55# where things appear on-disk
56log_must save_tunable DEDUP_LOG_TXG_MAX
57log_must set_tunable32 DEDUP_LOG_TXG_MAX 1
58log_must save_tunable DEDUP_LOG_FLUSH_ENTRIES_MIN
59log_must set_tunable32 DEDUP_LOG_FLUSH_ENTRIES_MIN 100000
60
61function cleanup
62{
63	if poolexists $POOL ; then
64		destroy_pool $POOL
65	fi
66	log_must rm -fd $VDEV_GENERAL $VDEV_DEDUP $MOUNTDIR
67	log_must restore_tunable DEDUP_LOG_TXG_MAX
68	log_must restore_tunable DEDUP_LOG_FLUSH_ENTRIES_MIN
69}
70
71
72function do_clean
73{
74	log_must destroy_pool $POOL
75	log_must rm -fd $VDEV_GENERAL $VDEV_DEDUP $MOUNTDIR
76}
77
78function do_setup
79{
80	log_must truncate -s 5G $VDEV_GENERAL
81	# Use 'xattr=sa' to prevent selinux xattrs influencing our accounting
82	log_must zpool create -f -O xattr=sa -m $MOUNTDIR $POOL $VDEV_GENERAL
83	log_must zfs set compression=off dedup=on $POOL
84}
85
86function dedup_table_size
87{
88	get_pool_prop dedup_table_size $POOL
89}
90
91function dedup_table_quota
92{
93	get_pool_prop dedup_table_quota $POOL
94}
95
96function ddt_entries
97{
98	typeset -i entries=$(zpool status -D $POOL | \
99		grep "dedup: DDT entries" | awk '{print $4}')
100
101	echo ${entries}
102}
103
104function ddt_add_entry
105{
106	count=$1
107	offset=$2
108	expand=$3
109
110	if [ -z "$offset" ]; then
111		offset=1
112	fi
113
114	for i in {$offset..$count}; do
115		echo "$i" > $MOUNTDIR/dedup-$i.txt
116	done
117	log_must sync_pool $POOL
118
119	log_note range $offset - $(( count + offset - 1 ))
120	log_note ddt_add_entry got $(ddt_entries)
121}
122
123# Create 6000 entries over three syncs
124function ddt_nolimit
125{
126	do_setup
127
128	log_note base ddt entries is $(ddt_entries)
129
130	ddt_add_entry 1
131	ddt_add_entry 100
132	ddt_add_entry 101 5000
133	ddt_add_entry 5001 6000
134
135	log_must test $(ddt_entries) -eq 6000
136
137	do_clean
138}
139
140function ddt_limit
141{
142	do_setup
143
144	log_note base ddt entries is $(ddt_entries)
145
146	log_must zpool set dedup_table_quota=32768 $POOL
147	ddt_add_entry 100
148
149	# it's possible to exceed dedup_table_quota over a single transaction,
150	# ensure that the threshold has been exceeded
151	cursize=$(dedup_table_size)
152	log_must test $cursize -gt $(dedup_table_quota)
153
154	# count the entries we have
155	log_must test $(ddt_entries) -ge 100
156
157	# attempt to add new entries
158	ddt_add_entry 101 200
159	log_must test $(ddt_entries) -eq 100
160	log_must test $cursize -eq $(dedup_table_size)
161
162	# remove all but one entry
163	for i in {2..100}; do
164		rm $MOUNTDIR/dedup-$i.txt
165	done
166	log_must sync_pool $POOL
167
168	log_must test $(ddt_entries) -eq 1
169	log_must test $cursize -gt $(dedup_table_size)
170	cursize=$(dedup_table_size)
171
172	log_must zpool set dedup_table_quota=none $POOL
173
174	# create more entries
175	zpool status -D $POOL
176	ddt_add_entry 101 200
177	log_must sync_pool $POOL
178
179	log_must test $(ddt_entries) -eq 101
180	log_must test $cursize -lt $(dedup_table_size)
181
182	do_clean
183}
184
185function ddt_dedup_vdev_limit
186{
187	do_setup
188
189	# add a dedicated dedup/special VDEV and enable an automatic quota
190	if (( RANDOM % 2 == 0 )) ; then
191		class="special"
192		size="200M"
193	else
194		class="dedup"
195		size="100M"
196	fi
197	log_must truncate -s $size $VDEV_DEDUP
198	log_must zpool add $POOL $class $VDEV_DEDUP
199	log_must zpool set dedup_table_quota=auto $POOL
200
201	log_must zfs set recordsize=1K $POOL
202
203	# Generate a working set to fill up the dedup/special allocation class
204	for i in {0..63}; do
205		log_must dd if=/dev/urandom of=$MOUNTDIR/file${i} bs=1M count=16
206		log_must sync_pool $POOL
207	done
208
209	zpool status -D $POOL
210	zpool list -v $POOL
211	echo DDT size $(dedup_table_size), with $(ddt_entries) entries
212
213	#
214	# With no DDT quota in place, the above workload will produce up to
215	# 1M of entries by using space in the normal class. With a quota, it
216	# should be well under 500,000. However, logged entries are hard to
217	# account for because they can appear on both logs, and can also
218	# represent an eventual removal. This isn't easily visible from
219	# outside, and even internally can result in going slightly over quota.
220	# For here, we just set the entry count a little higher than what we
221	# expect to allow for some instability.
222	#
223	log_must test $(ddt_entries) -le 650000
224
225	do_clean
226}
227
228log_onexit cleanup
229
230ddt_limit
231ddt_nolimit
232ddt_dedup_vdev_limit
233
234log_pass "DDT quota is enforced"
235