1#!/bin/sh
2# Copyright (c) 2019 Axcient
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions
6# are met:
7# 1. Redistributions of source code must retain the above copyright
8#    notice, this list of conditions and the following disclaimer.
9# 2. Redistributions in binary form must reproduce the above copyright
10#    notice, this list of conditions and the following disclaimer in the
11#    documentation and/or other materials provided with the distribution.
12#
13# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23# SUCH DAMAGE.
24#
25
26. $(atf_get_srcdir)/conf.sh
27
28atf_test_case add cleanup
29add_head()
30{
31	atf_set "descr" "Add a new path"
32	atf_set "require.user" "root"
33}
34add_body()
35{
36	load_gmultipath
37	load_dtrace
38
39	md0=$(alloc_md)
40	md1=$(alloc_md)
41	md2=$(alloc_md)
42	name=$(mkname)
43	atf_check -s exit:0 gmultipath create "$name" ${md0} ${md1}
44	check_multipath_state ${md0} "OPTIMAL" "ACTIVE" "PASSIVE" 
45
46	# Add a new path
47	atf_check -s exit:0 gmultipath add "$name" ${md2}
48	check_multipath_state ${md0} "OPTIMAL" "ACTIVE" "PASSIVE" "PASSIVE"
49}
50add_cleanup()
51{
52	common_cleanup
53}
54
55atf_test_case create_A cleanup
56create_A_head()
57{
58	atf_set "descr" "Create an Active/Active multipath device"
59	atf_set "require.user" "root"
60}
61create_A_body()
62{
63	load_gmultipath
64	load_dtrace
65
66	md0=$(alloc_md)
67	md1=$(alloc_md)
68	name=$(mkname)
69	atf_check -s exit:0 gmultipath create -A "$name" ${md0} ${md1}
70	check_multipath_state "${md1} ${md0}" "OPTIMAL" "ACTIVE" "ACTIVE" 
71}
72create_A_cleanup()
73{
74	common_cleanup
75}
76
77atf_test_case create_R cleanup
78create_R_head()
79{
80	atf_set "descr" "Create an Active/Read multipath device"
81	atf_set "require.user" "root"
82}
83create_R_body()
84{
85	load_gmultipath
86	load_dtrace
87
88	md0=$(alloc_md)
89	md1=$(alloc_md)
90	name=$(mkname)
91	atf_check -s exit:0 gmultipath create -R "$name" ${md0} ${md1}
92	check_multipath_state ${md0} "OPTIMAL" "ACTIVE" "READ" 
93}
94create_R_cleanup()
95{
96	common_cleanup
97}
98
99atf_test_case depart_and_arrive cleanup
100depart_and_arrive_head()
101{
102	atf_set "descr" "gmultipath should remove devices that disappear, and automatically reattach labeled providers that reappear"
103	atf_set "require.user" "root"
104}
105depart_and_arrive_body()
106{
107	load_gnop
108	load_gmultipath
109	md0=$(alloc_md)
110	md1=$(alloc_md)
111	name=$(mkname)
112	# We need a non-zero offset to gmultipath won't see the label when it
113	# tastes the md device.  We only want the label to be visible on the
114	# gnop device.
115	offset=131072
116	atf_check gnop create -o $offset /dev/${md0}
117	atf_check gnop create -o $offset /dev/${md1}
118	atf_check -s exit:0 gmultipath label "$name" ${md0}.nop
119	# gmultipath is too smart to let us create a gmultipath device by label
120	# when the two providers aren't actually related.  So we create a
121	# device by label with one provider, and then manually add the second.
122	atf_check -s exit:0 gmultipath add "$name" ${md1}.nop
123	NDEVS=`gmultipath list "$name" | grep -c 'md[0-9]*\.nop'`
124	atf_check_equal 2 $NDEVS
125
126	# Now fail the labeled provider
127	atf_check -s exit:0 gnop destroy -f ${md0}.nop
128	# It should be automatically removed from the multipath device
129	NDEVS=`gmultipath list "$name" | grep -c 'md[0-9]*\.nop'`
130	atf_check_equal 1 $NDEVS
131
132	# Now return the labeled provider
133	atf_check gnop create -o $offset /dev/${md0}
134	# It should be automatically restored to the multipath device.  We
135	# don't really care which path is active.
136	NDEVS=`gmultipath list "$name" | grep -c 'md[0-9]*\.nop'`
137	atf_check_equal 2 $NDEVS
138	STATE=`gmultipath list "$name" | awk '/^State:/ {print $2}'`
139	atf_check_equal "OPTIMAL" $STATE
140}
141depart_and_arrive_cleanup()
142{
143	common_cleanup
144}
145
146
147atf_test_case fail cleanup
148fail_head()
149{
150	atf_set "descr" "Manually fail a path"
151	atf_set "require.user" "root"
152}
153fail_body()
154{
155	load_gmultipath
156	md0=$(alloc_md)
157	md1=$(alloc_md)
158	name=$(mkname)
159	atf_check -s exit:0 gmultipath create "$name" ${md0} ${md1}
160	check_multipath_state ${md0} "OPTIMAL" "ACTIVE" "PASSIVE" 
161	# Manually fail the active path
162	atf_check -s exit:0 gmultipath fail "$name" ${md0}
163	check_multipath_state ${md1} "DEGRADED" "FAIL" "ACTIVE" 
164}
165fail_cleanup()
166{
167	common_cleanup
168}
169
170atf_test_case fail_on_error cleanup
171fail_on_error_head()
172{
173	atf_set "descr" "An error in the provider will cause gmultipath to mark it as FAIL"
174	atf_set "require.user" "root"
175}
176fail_on_error_body()
177{
178	load_gnop
179	load_gmultipath
180	md0=$(alloc_md)
181	md1=$(alloc_md)
182	name=$(mkname)
183	atf_check gnop create /dev/${md0}
184	atf_check gnop create /dev/${md1}
185	atf_check -s exit:0 gmultipath create "$name" ${md0}.nop ${md1}.nop
186	# The first I/O to the first path should fail, causing gmultipath to
187	# fail over to the second path.
188	atf_check gnop configure -r 100 -w 100 ${md0}.nop
189	atf_check -s exit:0 -o ignore -e ignore dd if=/dev/zero of=/dev/multipath/"$name" bs=4096 count=1
190	check_multipath_state ${md1}.nop "DEGRADED" "FAIL" "ACTIVE" 
191}
192fail_on_error_cleanup()
193{
194	common_cleanup
195}
196
197atf_test_case physpath cleanup
198physpath_head()
199{
200	atf_set "descr" "gmultipath should append /mp to the underlying providers' physical path"
201	atf_set "require.user" "root"
202}
203physpath_body()
204{
205	load_gnop
206	load_gmultipath
207	md0=$(alloc_md)
208	md1=$(alloc_md)
209	name=$(mkname)
210	physpath="some/physical/path"
211	# Create two providers with the same physical paths, mimicing how
212	# multipathed SAS drives appear.  This is the normal way to use
213	# gmultipath.  If the underlying providers' physical paths differ,
214	# then you're probably using gmultipath wrong.
215	atf_check gnop create -z $physpath /dev/${md0}
216	atf_check gnop create -z $physpath /dev/${md1}
217	atf_check -s exit:0 gmultipath create "$name" ${md0}.nop ${md1}.nop
218	gmultipath_physpath=$(diskinfo -p multipath/"$name") 
219	atf_check_equal "$physpath/mp" "$gmultipath_physpath"
220}
221physpath_cleanup()
222{
223	common_cleanup
224}
225
226atf_test_case prefer cleanup
227prefer_head()
228{
229	atf_set "descr" "Manually select the preferred path"
230	atf_set "require.user" "root"
231}
232prefer_body()
233{
234	load_gmultipath
235	load_dtrace
236
237	md0=$(alloc_md)
238	md1=$(alloc_md)
239	md2=$(alloc_md)
240	name=$(mkname)
241	atf_check -s exit:0 gmultipath create "$name" ${md0} ${md1} ${md2}
242	check_multipath_state ${md0} "OPTIMAL" "ACTIVE" "PASSIVE" "PASSIVE"
243
244	# Explicitly prefer the final path
245	atf_check -s exit:0 gmultipath prefer "$name" ${md2}
246	check_multipath_state ${md2} "OPTIMAL" "PASSIVE" "PASSIVE" "ACTIVE"
247}
248prefer_cleanup()
249{
250	common_cleanup
251}
252
253atf_test_case restore cleanup
254restore_head()
255{
256	atf_set "descr" "Manually restore a failed path"
257	atf_set "require.user" "root"
258}
259restore_body()
260{
261	load_gmultipath
262	load_dtrace
263
264	md0=$(alloc_md)
265	md1=$(alloc_md)
266	name=$(mkname)
267	atf_check -s exit:0 gmultipath create "$name" ${md0} ${md1}
268
269	# Explicitly fail the first path
270	atf_check -s exit:0 gmultipath fail "$name" ${md0}
271	check_multipath_state ${md1} "DEGRADED" "FAIL" "ACTIVE" 
272
273	# Explicitly restore it
274	atf_check -s exit:0 gmultipath restore "$name" ${md0}
275	check_multipath_state ${md1} "OPTIMAL" "PASSIVE" "ACTIVE" 
276}
277restore_cleanup()
278{
279	common_cleanup
280}
281
282atf_test_case restore_on_error cleanup
283restore_on_error_head()
284{
285	atf_set "descr" "A failed path should be restored if an I/O error is encountered on all other active paths"
286	atf_set "require.user" "root"
287}
288restore_on_error_body()
289{
290	load_gnop
291	load_gmultipath
292	load_dtrace
293
294	md0=$(alloc_md)
295	md1=$(alloc_md)
296	name=$(mkname)
297	atf_check gnop create /dev/${md0}
298	atf_check gnop create /dev/${md1}
299	atf_check -s exit:0 gmultipath create "$name" ${md0}.nop ${md1}.nop
300	# Explicitly fail the first path
301	atf_check -s exit:0 gmultipath fail "$name" ${md0}.nop
302
303	# Setup the second path to fail on the next I/O
304	atf_check gnop configure -r 100 -w 100  ${md1}.nop
305	atf_check -s exit:0 -o ignore -e ignore \
306	    dd if=/dev/zero of=/dev/multipath/"$name" bs=4096 count=1
307
308	# Now the first path should be active, and the second should be failed
309	check_multipath_state ${md0}.nop "DEGRADED" "ACTIVE" "FAIL" 
310}
311restore_on_error_cleanup()
312{
313	common_cleanup
314}
315
316atf_test_case rotate cleanup
317rotate_head()
318{
319	atf_set "descr" "Manually rotate the active path"
320	atf_set "require.user" "root"
321}
322rotate_body()
323{
324	load_gmultipath
325	load_dtrace
326
327	md0=$(alloc_md)
328	md1=$(alloc_md)
329	md2=$(alloc_md)
330	name=$(mkname)
331	atf_check -s exit:0 gmultipath create "$name" ${md0} ${md1} ${md2}
332	check_multipath_state ${md0} "OPTIMAL" "ACTIVE" "PASSIVE" "PASSIVE"
333
334	# Explicitly rotate the paths
335	atf_check -s exit:0 gmultipath rotate "$name"
336	check_multipath_state ${md2} "OPTIMAL" "PASSIVE" "PASSIVE" "ACTIVE"
337	# Again
338	atf_check -s exit:0 gmultipath rotate "$name"
339	check_multipath_state ${md1} "OPTIMAL" "PASSIVE" "ACTIVE" "PASSIVE"
340	# Final rotation should restore original configuration
341	atf_check -s exit:0 gmultipath rotate "$name"
342	check_multipath_state ${md0} "OPTIMAL" "ACTIVE" "PASSIVE" "PASSIVE"
343}
344rotate_cleanup()
345{
346	common_cleanup
347}
348
349atf_init_test_cases()
350{
351	atf_add_test_case add
352	atf_add_test_case create_A
353	atf_add_test_case create_R
354	atf_add_test_case depart_and_arrive
355	atf_add_test_case fail
356	atf_add_test_case fail_on_error
357	atf_add_test_case physpath
358	atf_add_test_case prefer
359	atf_add_test_case restore
360	atf_add_test_case restore_on_error
361	atf_add_test_case rotate
362}
363