Multipath device manageability improvements
[zfs.git] / cmd / zpool_layout / zpool_layout
1 #!/bin/bash
2 #
3 # Direct-Attached Mode
4 # --------------------
5 # Set BUSES and HOST_PORTS to match the topology of your system.  As
6 # each port is enumerated it will be assigned the next channel name.
7 # The current script enumerates each port on a bus before moving on
8 # to enumerate the next bus.
9 #
10 # Every distribution, version of udev, and type of attached storage
11 # seems to result in slightly different formatting of the by-path
12 # name.   For this reason you may need to adjust the parsing below
13 # to suit your needs.  This is one of the reasons to use a custom
14 # /etc/zfs/zdev.conf file, it allows the by-path naming convention
15 # to change and still keep the simple <channel><rank> naming.
16 #
17 # SAS-Switch Mode
18 # -------------------------
19 # When the host accesses disk via SAS switches the combination of
20 # bus and port number does not necessarily uniquely identify a
21 # channel or disk drawer.  In this case we must resort to other
22 # means to infer the physical topology.  For a single-level network
23 # (i.e. no switch cascading) we can assign alphabetic channel labels
24 # based on the switch port number that the drawer is connected to.
25 # If support for more complex topologies is needed this script will
26 # need to be customized or replaced.
27 #
28 # In SAS-Switch mode (enabled with "-g switch" ) we require that
29 # udev has been configured to create per-disk symbolic links in
30 # /dev/disk/by-id of the form
31 # <label>-<UUID>-switch-port:<X>-slot:<Y>.  <label> is a string that
32 # depends on the subsystem that created the link and defaults to
33 # "dm-uuid-mpath" (this prefix is used by multipathd).  <UUID> is a
34 # unique identifier for the disk typically obtained from the scsi_id
35 # program.  <X> and <Y> denote the switch port and disk slot
36 # numbers, respectively, and are typically obtained from sysfs.
37
38 AWK=${AWK:-/usr/bin/awk}
39 CONFIG=${CONFIG:-/etc/zfs/zdev.conf}
40 BUSES=( 01 02 03 )
41 HOST_PORTS=( 4 0 )
42 SWITCH_PORTS=( 0 1 2 3 4 5 6 7 8 9 )
43 CHANNELS=( A B C D E F G H I J K L M N O P Q R S T U V W X Y Z )
44 TOPOLOGY="direct"
45 TRIGGER="no"
46 MAPPING=linux
47 LABEL=${LABEL:-"dm-uuid-mpath"}
48 DEV_DISK_DIR="/dev/disk/by-path"
49
50 shopt -s extglob
51
52 usage() {
53         cat << EOF
54 Usage: zpool_layout [-th] [-c file] [-b buses] [-o switch_ports]
55            [-p host_ports] [-n channels] [-m map] [-l label]
56            [-g direct|switch]
57   -c    Alternate config file [default=${CONFIG}]
58   -b    Enumerate buses [default="${BUSES[*]}"]
59   -o    Enumerate switch ports [default="${SWITCH_PORTS[*]}"]
60   -p    Enumerate host ports [default="${HOST_PORTS[*]}"]
61   -n    Channel names [default="A..Z"]
62   -g    Storage network topology [default="${TOPOLOGY}"]
63   -t    Trigger and wait for udev to settle [default=${TRIGGER}]
64   -l    Prefix of SAS-switch-mode device links [default=${LABEL}]
65   -m    Slot mapping [default=${MAPPING}]
66   -h    Show this message
67 EOF
68         exit 0
69 }
70
71 while getopts 'c:b:o:p:n:l:m:g:th' OPTION; do
72         case ${OPTION} in
73         c)
74                 CONFIG=${OPTARG}
75                 ;;
76         b)
77                 BUSES=(${OPTARG})
78                 ;;
79         o)
80                 SWITCH_PORTS=(${OPTARG})
81                 ;;
82         p)
83                 HOST_PORTS=(${OPTARG})
84                 ;;
85         n)
86                 CHANNELS=(${OPTARG})
87                 ;;
88         l)
89                 LABEL=${OPTARG}
90                 ;;
91         m)
92                 MAPPING=`readlink -e ${OPTARG}`
93                 ;;
94         g)
95                 TOPOLOGY=${OPTARG}
96                 ;;
97         t)
98                 TRIGGER=yes
99                 ;;
100         h)
101                 usage
102                 ;;
103         esac
104 done
105
106 # Verify mapping file exists if specified.
107 # Linux-Slot    Custom-Slot
108 if [ ${MAPPING} != "linux" ] && [ ! -e ${MAPPING} ]; then
109         echo "Error: Mapping file '${MAPPING}' does not exist"
110         exit 1
111 fi
112
113 # Save stdout as fd #8, then redirect stdout to the config file.
114 exec 8>&1
115 exec >${CONFIG}
116
117 map_slot() {
118         local LINUX_SLOT=$1
119         local MAPPED_SLOT=
120
121         if [ ${MAPPING} = "linux" ]; then
122                 MAPPED_SLOT=${LINUX_SLOT}
123         else
124                 MAPPED_SLOT=`${AWK} "\\$1 == ${LINUX_SLOT} && !/^#/ \
125                         { print \\$2; exit }" $MAPPING`
126         fi
127         printf "%d" ${MAPPED_SLOT}
128 }
129
130 # Generate host port layout table for comment header.
131 print_host_port_layout() {
132         echo "# ------------------ Host Port Layout ---------------------"
133         echo -n "#          "
134         for (( i=0; i<${#BUSES[*]}; i++ )); do
135                 printf "%-8d" ${BUSES[$i]}
136         done
137         echo
138
139         for (( i=0, k=0; i<${#HOST_PORTS[*]}; i++ )); do
140                 printf "# Port %-2d  " ${HOST_PORTS[$i]}
141
142                 for (( j=0; j<${#BUSES[*]}; j++, k++ )); do
143                         let k=$j*${#HOST_PORTS[*]}+$i
144                         printf "%-8s" ${CHANNELS[$k]}
145                 done
146                 echo
147         done
148         echo "#"
149 }
150
151 # Generate SAS switch port layout table for comment header.
152 print_switch_port_layout() {
153         echo "# --------------- SAS Switch Port Layout ------------------"
154         echo -n "# Switch Port   "
155         for (( i=0; i<${#SWITCH_PORTS[*]}; i++ )); do
156                 printf "%3d" ${SWITCH_PORTS[$i]}
157         done
158         echo
159         echo -n "# Channel       "
160         for (( i=0; i<${#SWITCH_PORTS[*]}; i++ )); do
161                 printf "%3s" ${CHANNELS[$i]}
162         done
163         echo
164         echo "#"
165 }
166
167 # Generate channel/disk layout table for comment header.
168 print_channel_layout() {
169         pushd ${DEV_DISK_DIR} >/dev/null
170         echo "# ----------------- Channel/Disk Layout -------------------"
171         echo "# Channel  Disks"
172         if [ ${TOPOLOGY} = "switch" ] ; then
173                 for (( i=0; i<${#SWITCH_PORTS[*]}; i++ )); do
174                         printf "# %-9s" ${CHANNELS[$i]}
175                         p=${SWITCH_PORTS[$i]}
176                         ls ${LABEL}-+([0-9a-f])-switch-port:${p}-slot:+([0-9]) \
177                                 2>/dev/null | cut -f3 -d':' | sort -u -n | \
178                                 xargs | tr ' ' ','
179                 done
180         else
181                 for (( i=0, k=0; i<${#BUSES[*]}; i++ )); do
182                         for (( j=0; j<${#HOST_PORTS[*]}; j++, k++ )); do
183                                 printf "# %-9s" ${CHANNELS[$k]}
184                                 ls *:${BUSES[$i]}:*:${HOST_PORTS[$j]}* \
185                                         2>/dev/null | cut -f7 -d'-' | \
186                                         sort -u -n | xargs | tr ' ' ','
187                         done
188                 done
189         fi
190         echo "#"
191         popd > /dev/null
192 }
193
194 # Generate mapping from <channel><rank> to by-path name.
195 map_shortname_to_by_path() {
196         pushd ${DEV_DISK_DIR} >/dev/null
197         for (( i=0, k=0; i<${#BUSES[*]}; i++ )); do
198                 for (( j=0; j<${#HOST_PORTS[*]}; j++, k++ )); do
199                         BYPATH=(`ls *:${BUSES[$i]}:*:${HOST_PORTS[$j]}* \
200                                 2>/dev/null | grep -v part | \
201                                 sort -n -k7 -t'-' | cut -f1-6 -d'-'`)
202                         SLOTS=(`ls *:${BUSES[$i]}:*:${HOST_PORTS[$j]}* \
203                                 2>/dev/null | grep -v part | \
204                                 sort -n -k7 -t'-' | cut -f7 -d'-'`)
205                         TMP_FILE=`mktemp`
206
207                         for (( l=0; l<${#SLOTS[*]}; l++ )); do
208                                 MAPPED_SLOT=`map_slot ${SLOTS[$l]}`
209                                 printf "%s%d\t%s-%d\n" \
210                                         ${CHANNELS[$k]} ${MAPPED_SLOT} \
211                                         ${BYPATH[$l]} ${SLOTS[$l]} >>${TMP_FILE}
212                         done
213
214                         echo
215                         echo -n "# Channel ${CHANNELS[$k]}, "
216                         echo "Bus ${BUSES[$i]}, Port ${HOST_PORTS[$j]}"
217                         cat ${TMP_FILE} | sort -n -k2 -t${CHANNELS[$k]}
218                         rm -f ${TMP_FILE}
219                 done
220         done
221         popd >/dev/null
222 }
223
224 # Generate mapping from <channel><rank> to by-id name.
225 map_shortname_to_by_id() {
226         pushd ${DEV_DISK_DIR} >/dev/null
227         for (( i=0; i<${#SWITCH_PORTS[*]}; i++ )); do
228                 p=${SWITCH_PORTS[$i]}
229                 BYID=(`ls ${LABEL}-+([0-9a-f])-switch-port:${p}-slot:+([0-9]) \
230                         2>/dev/null | grep -v part | sort -k3n -t':' | \
231                         cut -f1-2 -d':'`)
232                 SLOTS=(`ls ${LABEL}-+([0-9a-f])-switch-port:${p}-slot:+([0-9]) \
233                         2>/dev/null | grep -v part | sort -k3n -t':' | \
234                         cut -f3 -d':'`)
235                 TMP_FILE=`mktemp`
236
237                 for (( l=0; l<${#SLOTS[*]}; l++ )); do
238                         MAPPED_SLOT=`map_slot ${SLOTS[$l]}`
239                         printf "%s%d\t%s:%d\n" \
240                                 ${CHANNELS[$i]} ${MAPPED_SLOT} ${BYID[$l]} \
241                                 ${SLOTS[$l]} >>${TMP_FILE}
242                 done
243
244                 echo
245                 echo -n "# Channel ${CHANNELS[$i]}, "
246                 echo "SAS Switch Port ${SWITCH_PORTS[$i]}"
247                 cat ${TMP_FILE} | sort -n -k2 -t${CHANNELS[$i]}
248                 rm -f ${TMP_FILE}
249         done
250         popd > /dev/null
251 }
252
253 # Generate comment header.
254 echo "#"
255 echo "# Custom ${DEV_DISK_DIR}  to /dev/disk/zpool mapping, "
256 echo "# based of the following physical cable layout."
257 echo "#"
258
259 case ${TOPOLOGY} in
260         direct)
261                 print_host_port_layout
262                 print_channel_layout
263                 map_shortname_to_by_path
264                 ;;
265         switch)
266                 DEV_DISK_DIR="/dev/disk/by-id"
267                 print_switch_port_layout
268                 print_channel_layout
269                 map_shortname_to_by_id
270                 ;;
271 esac
272
273 # Restore stdout from fd #8 and close fd #8.
274 exec 1>&8 8>&-
275
276 if [ ${TRIGGER} = "yes" ]; then
277         udevadm trigger --action=change --subsystem-match=block
278         udevadm settle
279 fi
280
281 exit 0