xref: /freebsd/usr.sbin/ctladm/tests/port.sh (revision 582f787e2a5c3114819dfaa4611cb4762716f0a1)
1a1608e88SAlan Somers# SPDX-License-Identifier: BSD-2-Clause
2a1608e88SAlan Somers#
3a1608e88SAlan Somers# Copyright (c) 2024 Axcient
4a1608e88SAlan Somers# All rights reserved.
5a1608e88SAlan Somers#
6a1608e88SAlan Somers# Redistribution and use in source and binary forms, with or without
7a1608e88SAlan Somers# modification, are permitted provided that the following conditions
8a1608e88SAlan Somers# are met:
9a1608e88SAlan Somers# 1. Redistributions of source code must retain the above copyright
10a1608e88SAlan Somers#    notice, this list of conditions and the following disclaimer.
11a1608e88SAlan Somers# 2. Redistributions in binary form must reproduce the above copyright
12a1608e88SAlan Somers#    notice, this list of conditions and the following disclaimer in the
13a1608e88SAlan Somers#    documentation and/or other materials provided with the distribution.
14a1608e88SAlan Somers#
15a1608e88SAlan Somers# THIS DOCUMENTATION IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16a1608e88SAlan Somers# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17a1608e88SAlan Somers# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18a1608e88SAlan Somers# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19a1608e88SAlan Somers# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20a1608e88SAlan Somers# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21a1608e88SAlan Somers# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22a1608e88SAlan Somers# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23a1608e88SAlan Somers# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24a1608e88SAlan Somers# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25a1608e88SAlan Somers
269747d11dSAlan Somers# Things that aren't tested due to lack of kernel support:
279747d11dSAlan Somers# * Creating camsim ports
289747d11dSAlan Somers# * Creating tpc ports
299747d11dSAlan Somers# * Creating camtgt ports
309747d11dSAlan Somers# * Creating umass ports
319747d11dSAlan Somers
329747d11dSAlan Somers# TODO
339747d11dSAlan Somers# * Creating nvmf ports
349747d11dSAlan Somers# * Creating ha ports
359747d11dSAlan Somers# * Creating fc ports
369747d11dSAlan Somers
37afecc74cSAlan Somers# The PGTAG can be any 16-bit number.  The only constraint is that each
38afecc74cSAlan Somers# PGTAG,TARGET pair must be globally unique.
39afecc74cSAlan SomersPGTAG=30257
40afecc74cSAlan Somers
41afecc74cSAlan Somersload_cfiscsi() {
42afecc74cSAlan Somers	if ! kldstat -q -m cfiscsi; then
43afecc74cSAlan Somers		kldload cfiscsi || atf_skip "could not load cfscsi kernel mod"
44afecc74cSAlan Somers	fi
45afecc74cSAlan Somers}
46afecc74cSAlan Somers
479747d11dSAlan Somersskip_if_ctld() {
489747d11dSAlan Somers	if service ctld onestatus > /dev/null; then
499747d11dSAlan Somers		# If ctld is running on this server, let's not interfere.
509747d11dSAlan Somers		atf_skip "Cannot run this test while ctld is running"
519747d11dSAlan Somers	fi
529747d11dSAlan Somers}
539747d11dSAlan Somers
549747d11dSAlan Somerscleanup() {
559747d11dSAlan Somers	driver=$1
569747d11dSAlan Somers
57591de753SAlan Somers	if [ -e port-create.txt ]; then
58afecc74cSAlan Somers		case "$driver" in
59afecc74cSAlan Somers		"ioctl")
60afecc74cSAlan Somers			PORTNUM=`awk '/port:/ {print $2}' port-create.txt`
61afecc74cSAlan Somers			ctladm port -r -d $driver -p $PORTNUM
62afecc74cSAlan Somers			;;
63afecc74cSAlan Somers		"iscsi")
64afecc74cSAlan Somers			TARGET=`awk '/target:/ {print $2}' port-create.txt`
657f500273SAlan Somers			ctladm port -r -d $driver -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target=$TARGET
66afecc74cSAlan Somers			;;
67afecc74cSAlan Somers		esac
689747d11dSAlan Somers	fi
699747d11dSAlan Somers}
709747d11dSAlan Somers
719747d11dSAlan Somersatf_test_case create_ioctl cleanup
729747d11dSAlan Somerscreate_ioctl_head()
739747d11dSAlan Somers{
749747d11dSAlan Somers	atf_set "descr" "ctladm can create a new ioctl port"
759747d11dSAlan Somers	atf_set "require.user" "root"
76*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
779747d11dSAlan Somers}
789747d11dSAlan Somerscreate_ioctl_body()
799747d11dSAlan Somers{
809747d11dSAlan Somers	skip_if_ctld
819747d11dSAlan Somers
82591de753SAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "ioctl"
83591de753SAlan Somers	atf_check egrep -q "Port created successfully" port-create.txt
84591de753SAlan Somers	atf_check egrep -q "frontend: *ioctl" port-create.txt
85591de753SAlan Somers	atf_check egrep -q "port: *[0-9]+" port-create.txt
86591de753SAlan Somers	portnum=`awk '/port:/ {print $2}' port-create.txt`
87591de753SAlan Somers	atf_check -o save:portlist.txt ctladm portlist -qf ioctl
88591de753SAlan Somers	atf_check egrep -q "$portnum *YES *ioctl *ioctl" portlist.txt
899747d11dSAlan Somers}
909747d11dSAlan Somerscreate_ioctl_cleanup()
919747d11dSAlan Somers{
929747d11dSAlan Somers	cleanup ioctl
939747d11dSAlan Somers}
949747d11dSAlan Somers
95edbd489dSAlan Somersatf_test_case remove_ioctl_without_required_args cleanup
96edbd489dSAlan Somersremove_ioctl_without_required_args_head()
97edbd489dSAlan Somers{
98edbd489dSAlan Somers	atf_set "descr" "ctladm will gracefully fail to remove an ioctl target if required arguments are missing"
99edbd489dSAlan Somers	atf_set "require.user" "root"
100*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
101edbd489dSAlan Somers}
102edbd489dSAlan Somersremove_ioctl_without_required_args_body()
103edbd489dSAlan Somers{
104edbd489dSAlan Somers	skip_if_ctld
105edbd489dSAlan Somers
106edbd489dSAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "ioctl"
107edbd489dSAlan Somers	atf_check egrep -q "Port created successfully" port-create.txt
108edbd489dSAlan Somers	atf_check -s exit:1 -e match:"Missing required argument: port_id" ctladm port -r -d "ioctl"
109edbd489dSAlan Somers}
110edbd489dSAlan Somersremove_ioctl_without_required_args_cleanup()
111edbd489dSAlan Somers{
112edbd489dSAlan Somers	cleanup ioctl
113edbd489dSAlan Somers}
114edbd489dSAlan Somers
115afecc74cSAlan Somersatf_test_case create_iscsi cleanup
116afecc74cSAlan Somerscreate_iscsi_head()
117afecc74cSAlan Somers{
118afecc74cSAlan Somers	atf_set "descr" "ctladm can create a new iscsi port"
119afecc74cSAlan Somers	atf_set "require.user" "root"
120*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
121afecc74cSAlan Somers}
122afecc74cSAlan Somerscreate_iscsi_body()
123afecc74cSAlan Somers{
124afecc74cSAlan Somers	skip_if_ctld
125afecc74cSAlan Somers	load_cfiscsi
126afecc74cSAlan Somers
127afecc74cSAlan Somers	TARGET=iqn.2018-10.myhost.create_iscsi
128afecc74cSAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target="$TARGET"
129afecc74cSAlan Somers	echo "target: $TARGET" >> port-create.txt
130afecc74cSAlan Somers	atf_check egrep -q "Port created successfully" port-create.txt
131afecc74cSAlan Somers	atf_check egrep -q "frontend: *iscsi" port-create.txt
132afecc74cSAlan Somers	atf_check egrep -q "port: *[0-9]+" port-create.txt
133afecc74cSAlan Somers	atf_check -o save:portlist.txt ctladm portlist -qf iscsi
134afecc74cSAlan Somers	# Unlike the ioctl driver, the iscsi driver creates ports in a disabled
135afecc74cSAlan Somers	# state, so the port's lunmap may be set before enabling it.
136afecc74cSAlan Somers	atf_check egrep -q "$portnum *NO *iscsi *iscsi.*$TARGET" portlist.txt
137afecc74cSAlan Somers}
138afecc74cSAlan Somerscreate_iscsi_cleanup()
139afecc74cSAlan Somers{
140afecc74cSAlan Somers	cleanup iscsi
141afecc74cSAlan Somers}
142afecc74cSAlan Somers
143afecc74cSAlan Somersatf_test_case create_iscsi_alias cleanup
144afecc74cSAlan Somerscreate_iscsi_alias_head()
145afecc74cSAlan Somers{
146afecc74cSAlan Somers	atf_set "descr" "ctladm can create a new iscsi port with a target alias"
147afecc74cSAlan Somers	atf_set "require.user" "root"
148*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
149afecc74cSAlan Somers}
150afecc74cSAlan Somerscreate_iscsi_alias_body()
151afecc74cSAlan Somers{
152afecc74cSAlan Somers	skip_if_ctld
153afecc74cSAlan Somers	load_cfiscsi
154afecc74cSAlan Somers
155afecc74cSAlan Somers	TARGET=iqn.2018-10.myhost.create_iscsi_alias
156afecc74cSAlan Somers	ALIAS="foobar"
157afecc74cSAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target="$TARGET" -O cfiscsi_target_alias="$ALIAS"
158afecc74cSAlan Somers	echo "target: $TARGET" >> port-create.txt
159afecc74cSAlan Somers	atf_check egrep -q "Port created successfully" port-create.txt
160afecc74cSAlan Somers	atf_check egrep -q "frontend: *iscsi" port-create.txt
161afecc74cSAlan Somers	atf_check egrep -q "port: *[0-9]+" port-create.txt
162afecc74cSAlan Somers	atf_check -o save:portlist.txt ctladm portlist -qvf iscsi
163afecc74cSAlan Somers	atf_check egrep -q "cfiscsi_target_alias=$ALIAS" portlist.txt
164afecc74cSAlan Somers}
165afecc74cSAlan Somerscreate_iscsi_alias_cleanup()
166afecc74cSAlan Somers{
167afecc74cSAlan Somers	cleanup iscsi
168afecc74cSAlan Somers}
169afecc74cSAlan Somers
170afecc74cSAlan Somersatf_test_case create_iscsi_without_required_args
171afecc74cSAlan Somerscreate_iscsi_without_required_args_head()
172afecc74cSAlan Somers{
173afecc74cSAlan Somers	atf_set "descr" "ctladm will gracefully fail to create an iSCSI target if required arguments are missing"
174afecc74cSAlan Somers	atf_set "require.user" "root"
175*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
176afecc74cSAlan Somers}
177afecc74cSAlan Somerscreate_iscsi_without_required_args_body()
178afecc74cSAlan Somers{
179afecc74cSAlan Somers	skip_if_ctld
180afecc74cSAlan Somers	load_cfiscsi
181afecc74cSAlan Somers
182afecc74cSAlan Somers	TARGET=iqn.2018-10.myhost.create_iscsi
183afecc74cSAlan Somers	atf_check -s exit:1 -e match:"Missing required argument: cfiscsi_target" ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG
184afecc74cSAlan Somers	atf_check -s exit:1 -e match:"Missing required argument: cfiscsi_portal_group_tag" ctladm port -c -d "iscsi" -O cfiscsi_target=$TARGET
185afecc74cSAlan Somers}
186afecc74cSAlan Somers
1879747d11dSAlan Somersatf_test_case create_ioctl_options cleanup
1889747d11dSAlan Somerscreate_ioctl_options_head()
1899747d11dSAlan Somers{
1909747d11dSAlan Somers	atf_set "descr" "ctladm can set options when creating a new ioctl port"
1919747d11dSAlan Somers	atf_set "require.user" "root"
192*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
1939747d11dSAlan Somers}
1949747d11dSAlan Somerscreate_ioctl_options_body()
1959747d11dSAlan Somers{
1969747d11dSAlan Somers	skip_if_ctld
1979747d11dSAlan Somers
198591de753SAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "ioctl" -O pp=101 -O vp=102
199591de753SAlan Somers	atf_check egrep -q "Port created successfully" port-create.txt
200591de753SAlan Somers	atf_check egrep -q "frontend: *ioctl" port-create.txt
201591de753SAlan Somers	atf_check egrep -q "port: *[0-9]+" port-create.txt
202591de753SAlan Somers	portnum=`awk '/port:/ {print $2}' port-create.txt`
203591de753SAlan Somers	atf_check -o save:portlist.txt ctladm portlist -qf ioctl
204591de753SAlan Somers	if ! egrep -q '101[[:space:]]+102' portlist.txt; then
2059747d11dSAlan Somers		ctladm portlist
2069747d11dSAlan Somers		atf_fail "Did not create the port with the specified options"
2079747d11dSAlan Somers	fi
2089747d11dSAlan Somers}
2099747d11dSAlan Somerscreate_ioctl_options_cleanup()
2109747d11dSAlan Somers{
2119747d11dSAlan Somers	cleanup ioctl
2129747d11dSAlan Somers}
2139747d11dSAlan Somers
2149747d11dSAlan Somers
2159747d11dSAlan Somersatf_test_case disable_ioctl cleanup
2169747d11dSAlan Somersdisable_ioctl_head()
2179747d11dSAlan Somers{
2189747d11dSAlan Somers	atf_set "descr" "ctladm can disable an ioctl port"
2199747d11dSAlan Somers	atf_set "require.user" "root"
220*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
2219747d11dSAlan Somers}
2229747d11dSAlan Somersdisable_ioctl_body()
2239747d11dSAlan Somers{
2249747d11dSAlan Somers	skip_if_ctld
2259747d11dSAlan Somers
226591de753SAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "ioctl"
227591de753SAlan Somers	portnum=`awk '/port:/ {print $2}' port-create.txt`
228591de753SAlan Somers	atf_check -o save:portlist.txt ctladm portlist -qf ioctl
2299747d11dSAlan Somers	atf_check -o ignore ctladm port -o off -p $portnum
2309747d11dSAlan Somers	atf_check -o match:"^$portnum *NO" ctladm portlist -qf ioctl
2319747d11dSAlan Somers}
2329747d11dSAlan Somersdisable_ioctl_cleanup()
2339747d11dSAlan Somers{
2349747d11dSAlan Somers	cleanup ioctl
2359747d11dSAlan Somers}
2369747d11dSAlan Somers
2379747d11dSAlan Somersatf_test_case enable_ioctl cleanup
2389747d11dSAlan Somersenable_ioctl_head()
2399747d11dSAlan Somers{
2409747d11dSAlan Somers	atf_set "descr" "ctladm can enable an ioctl port"
2419747d11dSAlan Somers	atf_set "require.user" "root"
242*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
2439747d11dSAlan Somers}
2449747d11dSAlan Somersenable_ioctl_body()
2459747d11dSAlan Somers{
2469747d11dSAlan Somers	skip_if_ctld
2479747d11dSAlan Somers
248591de753SAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "ioctl"
249591de753SAlan Somers	portnum=`awk '/port:/ {print $2}' port-create.txt`
250591de753SAlan Somers	atf_check -o save:portlist.txt ctladm portlist -qf ioctl
2519747d11dSAlan Somers	atf_check -o ignore ctladm port -o off -p $portnum
2529747d11dSAlan Somers	atf_check -o ignore ctladm port -o on -p $portnum
2539747d11dSAlan Somers	atf_check -o match:"^$portnum *YES" ctladm portlist -qf ioctl
2549747d11dSAlan Somers}
2559747d11dSAlan Somersenable_ioctl_cleanup()
2569747d11dSAlan Somers{
2579747d11dSAlan Somers	cleanup ioctl
2589747d11dSAlan Somers}
2599747d11dSAlan Somers
2609747d11dSAlan Somersatf_test_case remove_ioctl
2619747d11dSAlan Somersremove_ioctl_head()
2629747d11dSAlan Somers{
2639747d11dSAlan Somers	atf_set "descr" "ctladm can remove an ioctl port"
2649747d11dSAlan Somers	atf_set "require.user" "root"
265*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
2669747d11dSAlan Somers}
2679747d11dSAlan Somersremove_ioctl_body()
2689747d11dSAlan Somers{
2699747d11dSAlan Somers	skip_if_ctld
2709747d11dSAlan Somers
271591de753SAlan Somers	# Specify exact pp and vp to make the post-removal portlist check
272591de753SAlan Somers	# unambiguous
273591de753SAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "ioctl" -O pp=10001 -O vp=10002
274591de753SAlan Somers	portnum=`awk '/port:/ {print $2}' port-create.txt`
275591de753SAlan Somers	atf_check -o save:portlist.txt ctladm portlist -qf ioctl
276591de753SAlan Somers	atf_check -o inline:"Port destroyed successfully\n" ctladm port -r -d ioctl -p $portnum
277591de753SAlan Somers	# Check that the port was removed.  A new port may have been added with
278591de753SAlan Somers	# the same ID, so match against the pp and vp numbers, too.
279591de753SAlan Somers	if ctladm portlist -qf ioctl | egrep -q "^${portnum} .*10001 *10002"; then
280591de753SAlan Somers		ctladm portlist -qf ioctl
281591de753SAlan Somers		atf_fail "port was not removed"
2829747d11dSAlan Somers	fi
2839747d11dSAlan Somers}
2849747d11dSAlan Somers
285afecc74cSAlan Somersatf_test_case remove_iscsi
286afecc74cSAlan Somersremove_iscsi_head()
287afecc74cSAlan Somers{
288afecc74cSAlan Somers	atf_set "descr" "ctladm can remove an iscsi port"
289afecc74cSAlan Somers	atf_set "require.user" "root"
290*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
291afecc74cSAlan Somers}
292afecc74cSAlan Somersremove_iscsi_body()
293afecc74cSAlan Somers{
294afecc74cSAlan Somers	skip_if_ctld
295afecc74cSAlan Somers	load_cfiscsi
296afecc74cSAlan Somers
297afecc74cSAlan Somers	TARGET=iqn.2018-10.myhost.remove_iscsi
298afecc74cSAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target="$TARGET"
299afecc74cSAlan Somers	portnum=`awk '/port:/ {print $2}' port-create.txt`
300afecc74cSAlan Somers	atf_check -o save:portlist.txt ctladm portlist -qf iscsi
301edbd489dSAlan Somers	atf_check -o inline:"Port destroyed successfully\n" ctladm port -r -d iscsi -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target="$TARGET"
302afecc74cSAlan Somers	# Check that the port was removed.  A new port may have been added with
303afecc74cSAlan Somers	# the same ID, so match against the target and tag, too.
304afecc74cSAlan Somers	PGTAGHEX=0x7631	# PGTAG in hex
305afecc74cSAlan Somers	if ctladm portlist -qf iscsi | egrep -q "^${portnum} .*$PGTAG +[0-9]+ +$TARGET,t,$PGTAGHEX"; then
306afecc74cSAlan Somers		ctladm portlist -qf iscsi
307afecc74cSAlan Somers		atf_fail "port was not removed"
308afecc74cSAlan Somers	fi
309afecc74cSAlan Somers}
310afecc74cSAlan Somers
311afecc74cSAlan Somersatf_test_case remove_iscsi_without_required_args cleanup
312afecc74cSAlan Somersremove_iscsi_without_required_args_head()
313afecc74cSAlan Somers{
314afecc74cSAlan Somers	atf_set "descr" "ctladm will gracefully fail to remove an iSCSI target if required arguments are missing"
315afecc74cSAlan Somers	atf_set "require.user" "root"
316*582f787eSOlivier Cochard	atf_set "require.progs" ctladm
317afecc74cSAlan Somers}
318afecc74cSAlan Somersremove_iscsi_without_required_args_body()
319afecc74cSAlan Somers{
320afecc74cSAlan Somers	skip_if_ctld
321afecc74cSAlan Somers	load_cfiscsi
322afecc74cSAlan Somers
323afecc74cSAlan Somers	TARGET=iqn.2018-10.myhost.remove_iscsi_without_required_args
324afecc74cSAlan Somers	atf_check -o save:port-create.txt ctladm port -c -d "iscsi" -O cfiscsi_portal_group_tag=$PGTAG -O cfiscsi_target="$TARGET"
325afecc74cSAlan Somers	echo "target: $TARGET" >> port-create.txt
326edbd489dSAlan Somers	atf_check -s exit:1 -e match:"Missing required argument: cfiscsi_portal_group_tag" ctladm port -r -d iscsi -O cfiscsi_target="$TARGET"
327edbd489dSAlan Somers	atf_check -s exit:1 -e match:"Missing required argument: cfiscsi_target" ctladm port -r -d iscsi -O cfiscsi_portal_group_tag=$PGTAG
328afecc74cSAlan Somers}
329afecc74cSAlan Somersremove_iscsi_without_required_args_cleanup()
330afecc74cSAlan Somers{
331afecc74cSAlan Somers	cleanup iscsi
332afecc74cSAlan Somers}
333afecc74cSAlan Somers
3349747d11dSAlan Somersatf_init_test_cases()
3359747d11dSAlan Somers{
3369747d11dSAlan Somers	atf_add_test_case create_ioctl
337afecc74cSAlan Somers	atf_add_test_case create_iscsi
338afecc74cSAlan Somers	atf_add_test_case create_iscsi_without_required_args
339afecc74cSAlan Somers	atf_add_test_case create_iscsi_alias
3409747d11dSAlan Somers	atf_add_test_case create_ioctl_options
3419747d11dSAlan Somers	atf_add_test_case disable_ioctl
3429747d11dSAlan Somers	atf_add_test_case enable_ioctl
3439747d11dSAlan Somers	atf_add_test_case remove_ioctl
344edbd489dSAlan Somers	atf_add_test_case remove_ioctl_without_required_args
345afecc74cSAlan Somers	atf_add_test_case remove_iscsi
346afecc74cSAlan Somers	atf_add_test_case remove_iscsi_without_required_args
3479747d11dSAlan Somers}
348