├── README.md └── diskmap.py /README.md: -------------------------------------------------------------------------------- 1 | # DiskMap 2 | 3 | OpenSolaris/OpenIndiana utility to manage drive and map wmn device name (c1txxx) to real drive using lsi sas controller — Read more 4 | 5 | This script will hopefully make your life easier if you use ZFS on OpenSolaris/OpenIndiana with LSI controllers and some kind of backplane. 6 | 7 | It can: 8 | 9 | * list connected drives, and see the mapping between wmn device name (c1txxx) and their physical location (controller, enclosure, slot); 10 | * turn the error led of a drive on and off (for easy identification of the drive in large enclosures with many disks); 11 | * be used as a pipe to enhance the output of programs like `iostat` to annotate disk information with the location of each disk. 12 | 13 | It's a work in progress. It works for me, but I don't have the time to polish or clean it up; so if you want to add your favourite feature or fix some nasty bug, feel free to contribute. 14 | 15 | # Requirements 16 | 17 | You have to get the sas2ircu tool. Install it in /usr/sbin, or edit the path in the file). 18 | An outdated revision is available [here](http://www.supermicro.com/support/faqs/data_lib/FAQ_9633_SAS2IRCU_Phase_5.0-5.00.00.00.zip). 19 | 20 | There is some work in progress for smartmontools support. 21 | 22 | 23 | # Usage & Available Commands 24 | 25 | Everything happens through a CLI: 26 | 27 | # ./diskmap.py 28 | Diskmap - berilia> 29 | 30 | You can also run individual commands directly when invoking the program: 31 | 32 | # ./diskmap.py ledoff all 33 | 34 | 35 | ## `discover` 36 | 37 | You need to run discover whenever the layout has changed (disk have been added/removed). 38 | It will populate a cache, which will in turn be used by other commands. 39 | 40 | Example: 41 | 42 | Diskmap - berilia> discover 43 | Warning : Got the serial 5629293 from prtconf, but can't find it in disk detected by sas2ircu (disk removed/not on backplane ?) 44 | Warning : Got the serial 5629293 from prtconf, but can't find it in disk detected by sas2ircu (disk removed/not on backplane ?) 45 | Warning : Got the disk /dev/rdsk/c3t0d0 from zpool status, but can't find it in disk detected by sas2ircu (disk removed ?) 46 | Warning : Got the disk /dev/rdsk/c3t1d0 from zpool status, but can't find it in disk detected by sas2ircu (disk removed ?) 47 | 48 | ## `disks` 49 | 50 | List all the disks, along with their associated slot, model, capacity, state, pool, and usage. 51 | 52 | The format for the slot is `::`. 53 | 54 | Example: 55 | 56 | Diskmap - berilia> disks 57 | 0:02:00 c1t500151795944D1F0d0 INTEL SSDSA2M080 80.0G Ready (RDY) rpool: mirror-0 / data: cache 58 | 0:02:01 c1t50014EE6013FA92Ad0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-12 59 | 0:02:02 c1t50014EE058037AA6d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-16 60 | 0:02:03 c1t5001517959419710d0 INTEL SSDSA2M080 80.0G Ready (RDY) rpool: mirror-0 / data: cache 61 | 0:02:04 c1t50014EE6AB8F0F8Cd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-11 62 | 0:02:05 c1t50014EE002AE45C0d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-16 63 | 0:02:06 c1t500151795941982Bd0 INTEL SSDSA2M080 80.0G Ready (RDY) rpool: mirror-0 / data: cache 64 | 0:02:07 c1t50014EE6AB8F2BC5d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-10 65 | 0:02:08 c1t50014EE0AD591F51d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-15 66 | 0:02:09 c1t50014EE600E466F2d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-14 67 | 0:02:10 c1t50014EE058037F90d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-15 68 | 0:02:11 c1t50014EE0AD00308Fd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-3 69 | 1:02:00 c1t50014EE00295E662d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-11 70 | 1:02:01 c1t50014EE00295E07Fd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-7 71 | 1:02:02 c1t50014EE0AD40CB43d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-2 72 | 1:02:03 c1t50014EE0AD40BE62d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-1 73 | 1:02:04 c1t50014EE600FE429Ed0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-13 74 | 1:02:05 c1t50014EE6ABA7418Fd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-10 75 | 1:02:06 c1t50014EE00295E6B4d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-7 76 | 1:02:07 c1t50014EE0AD40C469d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-2 77 | 1:02:08 c1t50014EE0AD40C373d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-1 78 | 1:02:09 c1t50014EE057EB1AA1d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-6 79 | 1:02:10 c1t50014EE65639A59Fd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-12 80 | 1:02:11 c1t50014EE6AB8EEE8Fd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-4 81 | 1:02:12 c1t50014EE600FE548Cd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: spares 82 | 1:02:13 c1t50014EE0AD40CDAEd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-3 83 | 1:02:14 c1t50014EE057EAEB0Fd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-6 84 | 1:02:15 c1t50014EE0AD3BF57Dd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-0 85 | 1:02:16 c1t50014EE600E4A577d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-13 86 | 1:02:17 c1t50014EE6ABEA298Fd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-9 87 | 1:02:18 c1t50014EE0AD3C068Cd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-0 88 | 1:02:19 c1t50014EE057E63E46d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-4 89 | 1:02:20 c1t50014EE057E64BBAd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-5 90 | 1:02:21 c1t50014EE057E6558Dd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-5 91 | 1:02:22 c1t50014EE6ABDA37BEd0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-9 92 | 1:02:23 c1t50014EE0AD592031d0 WDC WD2002FAEX-0 2.0T Ready (RDY) data: mirror-14 93 | Drives : 36 Total Capacity : 66.3T 94 | 95 | ## `ledon` & `ledoff` 96 | 97 | You can turn on and off the error LEDs of individual drives. 98 | 99 | Examples: 100 | 101 | Diskmap - berilia> ledon c1t50014EE6ABEA298Fd0 102 | Turning leds on : 1/1 103 | Diskmap - berilia> ledon 1:02:16 104 | Turning leds on : 1/1 105 | Diskmap - berilia> ledoff all 106 | Turning leds on : 36/36 107 | 108 | Various input format are supported; use completion to see them all. 109 | 110 | ## `enclosures` 111 | 112 | List detected enclosures on your system. The main purpose is to give you the enclosures ID and define aliases. 113 | 114 | Example: 115 | 116 | Diskmap - berilia> enclosures 117 | {'50030480:0070d090': {'controller': 1L, 118 | 'id': '50030480:0070d090', 119 | 'index': 1L, 120 | 'numslot': 8L}, 121 | '50030480:0070d320': {'controller': 0L, 122 | 'id': '50030480:0070d320', 123 | 'index': 1L, 124 | 'numslot': 8L}, 125 | '50030480:0075e67f': {'controller': 0L, 126 | 'id': '50030480:0075e67f', 127 | 'index': 2L, 128 | 'numslot': 13L}, 129 | '50030480:009f8c7f': {'controller': 1L, 130 | 'id': '50030480:009f8c7f', 131 | 'index': 2L, 132 | 'numslot': 25L}} 133 | 134 | ## `alias` 135 | 136 | Give an alias to an enclosure. You need the enclosure ID as shown by `enclosures`. 137 | 138 | Examples: 139 | 140 | Diskmap - berilia> alias 50030480:0075e67f BCK 141 | Diskmap - berilia> alias 50030480:009f8c7f FNT 142 | Diskmap - berilia> alias 143 | {'50030480:0075e67f': 'BCK', '50030480:009f8c7f': 'FNT'} 144 | 145 | ## `enumerate` 146 | 147 | Helper to generate zpool create command line. 148 | 149 | Examples: 150 | 151 | Diskmap - berilia> enumerate mirror BCK FNT 152 | Debug with drive path : mirror 0:03:00 0:02:00 mirror 0:03:01 0:02:01 mirror 0:03:02 0:02:02 mirror 0:03:03 0:02:03 mirror 0:03:04 0:02:04 mirror 0:03:05 0:02:05 mirror 0:03:06 0:02:06 mirror 0:03:07 0:02:07 mirror 0:03:08 0:02:08 mirror 0:03:09 0:02:09 mirror 0:03:10 0:02:10 mirror 0:03:11 0:02:11 mirror 0:03:12 153 | C/C this in your zpool create cmd line : mirror c1t50014EE00295E662d0 c1t500151795944D1F0d0 mirror c1t50014EE00295E07Fd0 c1t50014EE6013FA92Ad0 mirror c1t50014EE0AD40CB43d0 c1t50014EE058037AA6d0 mirror c1t50014EE0AD40BE62d0 c1t5001517959419710d0 mirror c1t50014EE600FE429Ed0 c1t50014EE6AB8F0F8Cd0 mirror c1t50014EE6ABA7418Fd0 c1t50014EE002AE45C0d0 mirror c1t50014EE00295E6B4d0 c1t500151795941982Bd0 mirror c1t50014EE0AD40C469d0 c1t50014EE6AB8F2BC5d0 mirror c1t50014EE0AD40C373d0 c1t50014EE0AD591F51d0 mirror c1t50014EE057EB1AA1d0 c1t50014EE600E466F2d0 mirror c1t50014EE6ABA30519d0 c1t50014EE058037F90d0 mirror c1t50014EE6AB8EEE8Fd0 c1t50014EE0AD00308Fd0 154 | 155 | Diskmap - headone> enumerate raidz2 0:3 0:4 0:3 0:4 0:3 0:4 156 | Debug with drive path : raidz2 0:03:03 0:04:00 0:03:04 0:04:01 0:03:05 0:04:02 raidz2 0:03:06 0:04:03 0:03:09 0:04:04 0:03:10 0:04:05 raidz2 0:03:11 0:04:06 0:03:12 0:04:07 0:03:13 0:04:08 raidz2 0:03:14 0:04:09 0:03:15 0:04:10 0:03:16 0:04:11 raidz2 0:03:17 0:04:12 0:03:18 0:04:13 0:03:19 0:04:14 raidz2 0:03:20 0:04:15 157 | C/C on your zpool create cmd line : raidz2 c8t50014EE5AAAC5B08d0 c8t50014EE555577A3Cd0 c8t50014EE55556F208d0 c8t50014EE500016C20d0 c8t50014EE555571580d0 c8t50014EE5AAAC37D8d0 raidz2 c8t5000A7203007963Ad0 c8t50014EE500018F04d0 c8t50014EE555576B44d0 c8t50014EE5AAACF084d0 c8t50014EE55556C0A0d0 c8t5000A72030079632d0 raidz2 c8t50014EE555564DD8d0 c8t50014EE5AAAC3270d0 c8t50014EE50001B748d0 c8t50014EE500026A8Cd0 c8t50014EE5AAAB3470d0 c8t50014EE5000227F8d0 raidz2 c8t50014EE5AAACF6A0d0 c8t50014EE5AAAC5574d0 c8t50014EE55557BEA0d0 c8t50014EE555568130d0 c8t50014EE5AAAC185Cd0 c8t50014EE5AAAC24ACd0 raidz2 c8t50014EE5AAABC21Cd0 c8t50014EE55555FDA4d0 c8t50014EE555577928d0 c8t50014EE5AAAC6010d0 c8t50014EE5AAAC8788d0 c8t50014EE555575130d0 158 | 159 | 160 | 161 | 162 | # STDIN mangling 163 | 164 | The program can also be used in a pipe, to enhance the standard output of another program to add device locations to the output of the program. 165 | 166 | Example (assuming the aliases defined above): 167 | 168 | # iostat -x -e -n 1 | ./diskmap.py 169 | extended device statistics ---- errors --- 170 | r/s w/s kr/s kw/s wait actv wsvc_t asvc_t %w %b s/w h/w trn tot device 171 | 0.0 0.0 0.0 1.0 0.0 0.0 2.2 0.2 0 0 0 0 0 0 c3t0d0 172 | 0.0 0.0 0.0 1.0 0.0 0.0 2.2 0.2 0 0 0 0 0 0 c3t1d0 173 | 6.1 1.1 78.5 89.2 0.0 0.0 0.0 2.4 0 0 2 2 5 9 c1t5001517959419710d0/BCK03 174 | 6.2 1.1 79.4 89.2 0.0 0.0 0.0 2.3 0 0 2 2 7 11 c1t500151795944D1F0d0/BCK00 175 | 6.1 1.1 78.6 89.3 0.0 0.0 0.0 2.4 0 0 2 3 13 18 c1t500151795941982Bd0/BCK06 176 | 2.9 10.2 57.0 174.7 0.0 0.0 0.0 1.7 0 1 2 0 0 2 c1t50014EE0AD00308Fd0/BCK11 177 | 2.8 9.6 56.2 172.8 0.0 0.0 0.0 3.0 0 1 2 0 0 2 c1t50014EE057EB1AA1d0/FNT09 178 | 2.8 9.8 56.1 174.5 0.0 0.0 0.0 2.9 0 1 2 0 0 2 c1t50014EE0AD40BE62d0/FNT03 179 | 0.6 3.0 12.4 34.2 0.0 0.0 0.0 2.2 0 0 2 0 0 2 c1t50014EE00295E662d0/FNT00 180 | 2.8 9.8 56.3 174.5 0.0 0.0 0.0 3.0 0 1 2 0 0 2 c1t50014EE0AD40C373d0/FNT08 181 | 2.8 9.9 56.3 174.4 0.0 0.0 0.0 2.9 0 1 2 0 0 2 c1t50014EE0AD40CB43d0/FNT02 182 | 2.8 9.7 56.4 174.5 0.0 0.0 0.0 3.0 0 1 2 0 0 2 c1t50014EE00295E6B4d0/FNT06 183 | 3.0 9.9 68.1 175.5 0.0 0.0 0.0 3.3 0 1 2 0 0 2 c1t50014EE057E63E46d0/FNT19 184 | 2.8 9.9 56.4 174.4 0.0 0.0 0.0 2.9 0 1 2 0 0 2 c1t50014EE0AD40C469d0/FNT07 185 | 2.8 9.3 55.4 169.4 0.0 0.0 0.0 3.0 0 1 2 0 0 2 c1t50014EE057E64BBAd0/FNT20 186 | 2.8 10.0 56.5 175.6 0.0 0.0 0.0 2.9 0 1 2 0 0 2 c1t50014EE0AD3C068Cd0/FNT18 187 | 2.8 9.3 55.6 169.4 0.0 0.0 0.0 3.0 0 1 2 0 0 2 c1t50014EE057E6558Dd0/FNT21 188 | 2.9 10.0 56.7 175.6 0.0 0.0 0.0 2.9 0 1 2 0 0 2 c1t50014EE0AD3BF57Dd0/FNT15 189 | 2.8 9.9 56.6 174.7 0.0 0.0 0.0 2.9 0 1 2 0 0 2 c1t50014EE0AD40CDAEd0/FNT13 190 | 2.9 9.5 56.7 172.8 0.0 0.0 0.0 3.0 0 1 2 0 0 2 c1t50014EE057EAEB0Fd0/FNT14 191 | 2.8 9.7 56.5 174.5 0.0 0.0 0.0 3.0 0 1 2 0 0 2 c1t50014EE00295E07Fd0/FNT01 192 | 1.6 7.8 36.7 94.5 0.0 0.0 0.0 1.7 0 0 2 0 0 2 c1t50014EE600E466F2d0/BCK09 193 | 1.6 7.7 36.5 93.9 0.0 0.0 0.0 1.6 0 0 2 0 0 2 c1t50014EE058037F90d0/BCK10 194 | 1.6 7.4 37.1 91.8 0.0 0.0 0.0 1.6 0 0 2 0 0 2 c1t50014EE6013FA92Ad0/BCK01 195 | 1.5 7.4 35.9 91.8 0.0 0.0 0.0 1.6 0 0 2 0 0 2 c1t50014EE058037AA6d0/BCK02 196 | 1.5 8.5 35.0 100.8 0.0 0.0 0.0 2.3 0 0 2 0 0 2 c1t50014EE6ABA7418Fd0/FNT05 197 | 1.5 7.4 36.0 91.8 0.0 0.0 0.0 1.6 0 0 2 0 0 2 c1t50014EE002AE45C0d0/BCK05 198 | 1.5 7.8 34.0 94.4 0.0 0.0 0.0 2.4 0 0 2 0 0 2 c1t50014EE600FE429Ed0/FNT04 199 | 1.5 7.9 36.0 93.1 0.0 0.0 0.0 1.7 0 0 2 0 0 2 c1t50014EE6AB8F0F8Cd0/BCK04 200 | 1.5 7.4 34.2 91.8 0.0 0.0 0.0 2.6 0 0 2 9 32 43 c1t50014EE65639A59Fd0/FNT10 201 | 1.7 9.0 36.2 107.5 0.0 0.0 0.0 2.4 0 0 2 0 0 2 c1t50014EE6ABEA298Fd0/FNT17 202 | 1.6 7.7 36.6 93.9 0.0 0.0 0.0 1.6 0 0 2 0 0 2 c1t50014EE0AD591F51d0/BCK08 203 | 1.6 8.4 37.3 100.8 0.0 0.0 0.0 1.6 0 0 2 0 0 2 c1t50014EE6AB8F2BC5d0/BCK07 204 | 1.5 7.8 33.8 94.4 0.0 0.0 0.0 2.5 0 0 2 0 0 2 c1t50014EE600E4A577d0/FNT16 205 | 1.5 7.8 33.7 94.5 0.0 0.0 0.0 2.4 0 0 2 0 0 2 c1t50014EE0AD592031d0/FNT23 206 | 1.7 9.0 36.4 107.5 0.0 0.0 0.0 2.4 0 1 2 0 0 2 c1t50014EE6ABDA37BEd0/FNT22 207 | 2.2 9.3 47.3 122.1 0.0 0.0 0.0 3.2 0 1 2 0 0 2 c1t50014EE6AB8EEE8Fd0/FNT11 208 | 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.1 0 0 0 0 0 0 c1t50014EE600FE548Cd0/FNT12 209 | 210 | See how BCK and FNT (as well as drive number in the enclosure) has been added to the output. 211 | 212 | # Know Limitation 213 | 214 | At the day of writing, sas2ircu refuse to list your devices if you have more than 256 devices. 215 | 216 | Multipath of enclosure sometime have multiple sas address. It should be easy to fix, but I didn't found any document which explain why it change and how much it can change, so right now diskmap don't handle it. 217 | 218 | -------------------------------------------------------------------------------- /diskmap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding: utf-8 -*- 3 | # 4 | # 5 | # Copyright (C) 2011 Sébastien Wacquiez 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | 21 | 22 | 23 | VERSION="0.12b" 24 | 25 | import subprocess, re, os, sys, cmd, pickle, glob 26 | 27 | try: 28 | import readline 29 | except ImportError: 30 | pass 31 | from pprint import pformat, pprint 32 | pj = os.path.join 33 | 34 | from socket import gethostname 35 | hostname = gethostname() 36 | 37 | cachefile = "/tmp/pouet" 38 | 39 | sas2ircu = "/usr/sbin/sas2ircu" 40 | prtconf = "/usr/sbin/prtconf" 41 | zpool = "/usr/sbin/zpool" 42 | smartctl = "/usr/local/sbin/smartctl" 43 | mdb = "/usr/bin/mdb" 44 | 45 | def run(cmd, args, tosend=""): 46 | if not isinstance(args, list): 47 | args = [ args ] 48 | if not os.path.exists(cmd): 49 | raise Exception("Executable %s not found, please provide absolute path"%cmd) 50 | args = tuple([ str(i) for i in args ]) 51 | if tosend: 52 | process = subprocess.Popen((cmd,) + args, 53 | stdout=subprocess.PIPE, 54 | stdin=subprocess.PIPE) 55 | return process.communicate(tosend)[0] 56 | else: 57 | return subprocess.Popen((cmd,) + args, 58 | stdout=subprocess.PIPE).communicate()[0] 59 | 60 | 61 | def revert(mydict): 62 | return dict([ (v,k) for k,v in mydict.items()]) 63 | 64 | def cleandict(mydict, *toint): 65 | result = {} 66 | for k in mydict.keys(): 67 | if k in toint: 68 | result[k] = long(mydict[k]) 69 | elif isinstance(mydict[k], str): 70 | result[k] = mydict[k].strip() 71 | else: 72 | result[k] = mydict[k] 73 | return result 74 | 75 | def megabyze(i, fact=1000): 76 | """ 77 | Return the size in Kilo, Mega, Giga, Tera, Peta according to the input. 78 | """ 79 | i = float(i) 80 | for unit in "", "K", "M", "G", "T", "P": 81 | if i < 2000: break 82 | i = i / fact 83 | return "%.1f%s"%(i, unit) 84 | 85 | class SesManager(cmd.Cmd): 86 | def __init__(self, *l, **kv): 87 | cmd.Cmd.__init__(self, *l, **kv) 88 | self._enclosures = {} 89 | self._controllers = {} 90 | self._disks = {} 91 | self.aliases = {} 92 | self.prompt = "Diskmap - %s> "%hostname 93 | 94 | @property 95 | def disks(self): 96 | return dict([ (k, v) for k, v in self._disks.items() if k.startswith("/dev/rdsk/") ]) 97 | 98 | @property 99 | def enclosures(self): 100 | return self._enclosures 101 | 102 | @property 103 | def controllers(self): 104 | return self._controllers 105 | 106 | def discover_controllers(self, fromstring=None): 107 | """ Discover controller present in the computer """ 108 | if not fromstring: 109 | fromstring = run(sas2ircu, "LIST") 110 | tmp = re.findall("(\n +[0-9]+ +.*)", fromstring) 111 | for ctrl in tmp: 112 | ctrl = ctrl.strip() 113 | m = re.match("(?P[0-9]) +(?P[^ ].*[^ ]) +(?P[^ ]+) +" 114 | "(?P[^ ]+) +(?P[^ ]*:[^ ]*) +(?P[^ ]+) +" 115 | "(?P[^ ]+) *", ctrl) 116 | if m: 117 | m = cleandict(m.groupdict(), "id") 118 | self._controllers[m["id"]] = m 119 | 120 | def discover_enclosures(self, ctrls = None): 121 | """ Discover enclosure wired to controller. Ctrls = { 0: 'sas2ircu output', 1: 'sas2ircu output', ...}""" 122 | if not ctrls: 123 | tmp = {} 124 | for ctrl in self.controllers.keys(): 125 | tmp[ctrl] = run(sas2ircu, [ctrl, "DISPLAY"]) 126 | ctrls = tmp 127 | for ctrl, output in ctrls.items(): 128 | enclosures = {} 129 | # Discover enclosures 130 | for m in re.finditer("Enclosure# +: (?P[^ ]+)\n +" 131 | "Logical ID +: (?P[^ ]+)\n +" 132 | "Numslots +: (?P[0-9]+)", output): 133 | m = cleandict(m.groupdict(), "index", "numslot") 134 | m["id"] = m["id"].lower() 135 | 136 | # Compute path, save ctrl 137 | m["path"] = [ "%s:%s"%(ctrl,m["index"] ) ] 138 | m["controller"] = ctrl 139 | 140 | # And if we already have this enclosure, just add it to the existing one, else save it 141 | if m["id"] in self._enclosures: 142 | self._enclosures[m["id"]]["path"].extend(m["path"]) 143 | enclosures[m["index"]] = self._enclosures[m["id"]] 144 | else: 145 | self._enclosures[m["id"]] = m 146 | enclosures[m["index"]] = m 147 | 148 | # Discover Drives on each enclosure 149 | for m in re.finditer("Device is a Hard disk\n +" 150 | "Enclosure # +: (?P[^\n]*)\n +" 151 | "Slot # +: (?P[^\n]*)\n +" 152 | "(SAS Address +: (?P[^\n]*)\n +)?" 153 | "State +: (?P[^\n]*)\n +" 154 | "Size .in MB./.in sectors. +: (?P[^/]*)/(?P[^\n]*)\n +" 155 | "Manufacturer +: (?P[^\n]*)\n +" 156 | "Model Number +: (?P[^\n]*)\n +" 157 | "Firmware Revision +: (?P[^\n]*)\n +" 158 | "Serial No +: (?P[^\n]*)\n +" 159 | "(GUID +: (?P[^\n]*)\n +)?" 160 | "Protocol +: (?P[^\n]*)\n +" 161 | "Drive Type +: (?P[^\n]*)\n" 162 | , output): 163 | m = cleandict(m.groupdict(), "enclosureindex", "slot", "sizemb", "sizesector") 164 | m["enclosure"] = enclosures[m["enclosureindex"]]["id"] 165 | # Uppercase the serial number (I really don't know if it's a good idea ...) 166 | m["serial"] = m["serial"].upper() 167 | # Set the controller and full path. A device can be multiple attached 168 | m["controller"] = [ ctrl ] 169 | m["path"] = [ "%1d:%.2d:%.2d"%(ctrl, m["enclosureindex"], m["slot"]) ] 170 | # If we already have this disk, just add the path to the existing object 171 | if m["serial"] in self._disks: 172 | self._disks[m["serial"]]["path"].extend(m["path"]) 173 | self._disks[m["serial"]]["controller"].extend(m["controller"]) 174 | else: # else save it 175 | self._disks[m["serial"]] = m 176 | 177 | def discover_mapping(self, fromstring=None): 178 | """ 179 | Use prtconf to get real device name using disk serial. 180 | We should be able to use guid instead, but for some reason it's not reported 181 | in standard way on the server I've access to. 182 | 183 | """ 184 | if not fromstring: 185 | fromstring = run(prtconf, "-v") 186 | # Do some ugly magic to get what we want 187 | # First, get one line per drive 188 | tmp = fromstring.replace("\n", "").replace("disk, instance", "\n") 189 | # Then match with regex 190 | tmp = re.findall("name='inquiry-serial-no' type=string items=1 dev=none +value='([^']+)'" 191 | ".*?" 192 | #"name='client-guid' type=string items=1 *value='([^']+)'" 193 | #".*?" 194 | "dev_link=(/dev/rdsk/c[^ ]*d0)s0", tmp) 195 | for serial, device in tmp: 196 | # We use a upped serial. 197 | serial = serial.strip().upper() 198 | # Sometimes serial returned by prtconf and by sas2ircu are different. 199 | if serial not in self._disks: 200 | # First, try to mangle them (observed on WD disk) 201 | if serial.replace("-", "") in self._disks: 202 | serial = serial.replace("-", "") 203 | # Then try to use just 8 first char (observed on Seagate Drive) 204 | elif serial[:8] in self._disks: 205 | serial = serial[:8] 206 | # Then try to use just 8 last char (observed on some other WD disk) 207 | elif serial[-8:] in self._disks: 208 | serial = serial[-8:] 209 | # Then try to add "WD" in front of the serial (observed on WD disk) 210 | elif "WD"+serial in self._disks: 211 | serial = "WD"+serial 212 | if serial in self._disks: 213 | # Add device name to disks 214 | if "device" in self._disks[serial]: 215 | print "Warning ! We have 2 device for disk %s : %s and %s"%(serial, self._disks[serial]["device"], device) 216 | print "Check your mutlipath settings (stmsboot -e and scsi-vhci-failover-override in /kernel/drv/scsi_vhci.conf" 217 | self._disks[serial]["device"] = device 218 | # Add a reverse lookup 219 | self._disks[device] = self._disks[serial] 220 | else: 221 | print "Warning : Got the serial %s from prtconf, but can't find it in disk detected by sas2ircu (disk removed/not on backplane ?)"%serial 222 | 223 | def discover_zpool(self, fromstring=None): 224 | """ Try to locate disk in current zpool configuration""" 225 | if not fromstring: 226 | fromstring = run(zpool, "status") 227 | pools = fromstring.split("pool:") 228 | for pool in pools: 229 | if not pool.strip(): continue 230 | for m in re.finditer(" (?P[^\n]+)\n *" # We've splitted on pool:, so our first word is the pool name 231 | "state: (?P[^ ]+)\n *" 232 | "(status: (?P(.|\n)+)\n *)??" 233 | "scan: (?P(.|\n)*)\n *" 234 | "config: ?(?P(.|\n)*)\n *" 235 | "errors: (?P[^\n]*)" 236 | ,pool): 237 | m = m.groupdict() 238 | parent = "stripped" 239 | for disk in re.finditer("(?P[ \t]+)(?P[^ \t\n]+)( +(?P[^ \t\n]+) +)?(" 240 | "(?P[^ \t\n]+) +(?P[^ \t\n]+) +" 241 | "(?P[^\n]+))?(?P[^\n]+)?\n", m["config"]): 242 | disk = disk.groupdict() 243 | if not disk["name"] or disk["name"] in ("NAME", m["pool"]): 244 | continue 245 | if disk["name"][-4:-2] == "d0": 246 | disk["name"] = disk["name"][:-2] 247 | if (disk["name"].startswith("mirror") or 248 | disk["name"].startswith("log") or 249 | disk["name"].startswith("raid") or 250 | disk["name"].startswith("spare") or 251 | disk["name"].startswith("cache")): 252 | parent = disk["name"].strip() 253 | continue 254 | if "/dev/rdsk" not in disk["name"]: 255 | disk["name"] = "/dev/rdsk/%s"%disk["name"] 256 | if disk["name"] not in self._disks: 257 | print "Warning : Got the disk %s from zpool status, but can't find it in disk detected by sas2ircu (disk removed ?)"%disk["name"] 258 | continue 259 | self._disks[disk["name"]]["zpool"] = self._disks[disk["name"]].get("zpool", {}) 260 | self._disks[disk["name"]]["zpool"][m["pool"]] = parent 261 | 262 | def set_leds(self, disks, value=True): 263 | if isinstance(disks, dict): 264 | disks = disks.values() 265 | progress = xrange(1,len(disks)+1, 1).__iter__() 266 | value = "on" if value else "off" 267 | for disk in disks: 268 | print "\rTurning leds %s : %3d/%d"%(value, progress.next(),len(disks)), 269 | run(sas2ircu, [disk["controller"][0], "LOCATE", "%(enclosureindex)s:%(slot)s"%disk, value]) 270 | print 271 | 272 | def preloop(self): 273 | try: 274 | self.do_load() 275 | except: 276 | print "Loading of previous save failed, trying to discover" 277 | self.do_discover() 278 | self.do_save() 279 | 280 | def emptyline(self): 281 | self.do_help("") 282 | 283 | def do_quit(self, line): 284 | "Quit" 285 | return True 286 | do_EOF = do_quit 287 | 288 | def complete_discover(self, text, line, begidx, endidx): 289 | # Basic completion, cannot list anyting else than the current rep 290 | candidates = [ i for i in os.listdir(".") if os.path.isdir(i) ] 291 | return [ i for i in candidates if i.startswith(text) ] 292 | 293 | def do_discover(self, configdir=""): 294 | """Perform discovery on host to populate controller, enclosures and disks 295 | 296 | Take an optionnal parameter which can be a directory containing files dumped 297 | with confidump. 298 | """ 299 | self._enclosures = {} 300 | self._controllers = {} 301 | self._disks = {} 302 | if configdir and os.path.isdir(configdir): 303 | # We wan't to load data from an other box for testing purposes 304 | # So we don't want to catch any exception 305 | files = os.listdir(configdir) 306 | for f in ("prtconf-v.txt", "sas2ircu-0-display.txt", "sas2ircu-list.txt", "zpool-status.txt"): 307 | if f not in files: 308 | print "Invalid confdir, lacking of %s"%f 309 | return 310 | self.discover_controllers(file(pj(configdir, "sas2ircu-list.txt")).read()) 311 | files = glob.glob(pj(configdir, "sas2ircu-*-display.txt")) 312 | tmp = {} 313 | for name in files: 314 | ctrlid = long(os.path.basename(name).split("-")[1]) 315 | tmp[ctrlid] = file(name).read() 316 | self.discover_enclosures(tmp) 317 | self.discover_mapping(file(pj(configdir, "prtconf-v.txt")).read()) 318 | self.discover_zpool(file(pj(configdir, "zpool-status.txt")).read()) 319 | else: 320 | for a in ( "discover_controllers", "discover_enclosures", 321 | "discover_mapping", "discover_zpool" ): 322 | try: 323 | getattr(self, a)() 324 | except Exception, e: 325 | print "Got an error during %s discovery : %s"%(a,e) 326 | print "Please run %s configdump and send the report to dev"%sys.argv[0] 327 | break 328 | self.do_save() 329 | do_refresh = do_discover 330 | 331 | def do_save(self, line=cachefile): 332 | """Save data to cache file. Use file %s if not specified"""%cachefile 333 | if not line: line = cachefile # Cmd pass a empty string 334 | pickle.dump((self.controllers, self.enclosures, self._disks, self.aliases), file(line, "w+")) 335 | 336 | 337 | def do_load(self, line=cachefile): 338 | """Load data from cache file. Use file %s if not specified"""%cachefile 339 | self._controllers, self._enclosures, self._disks, self.aliases = pickle.load(file(line)) 340 | 341 | def do_enclosures(self, line): 342 | """Display detected enclosures""" 343 | pprint(self.enclosures) 344 | 345 | def do_controllers(self, line): 346 | """Display detected controllers""" 347 | pprint(self.controllers) 348 | 349 | def do_disks(self, line): 350 | """Display detected disks. Use -v for verbose output""" 351 | list = [ (",".join(v["path"]), v) 352 | for k,v in self.disks.items() ] 353 | list.sort() 354 | if line == "-v": 355 | pprint (list) 356 | return 357 | totalsize = 0 358 | for path, disk in list: 359 | disk["cpath"] = path 360 | disk["device"] = disk["device"].replace("/dev/rdsk/", "") 361 | disk["readablesize"] = megabyze(disk["sizemb"]*1024*1024) 362 | disk["pzpool"] = " / ".join([ "%s: %s"%(k,v) for k,v in disk.get("zpool", {}).items() ]) 363 | disk["alias"] = self.aliases.get(disk["enclosure"], disk["enclosure"]) + "/%02d"%disk["slot"] 364 | totalsize += disk["sizemb"]*1024*1024 365 | print "{cpath} {alias:<16} {device:<21} {model:<16} {readablesize:<6} {pzpool}".format(**disk) 366 | print "Drives : %s Total Capacity : %s"%(len(self.disks), megabyze(totalsize)) 367 | 368 | def smartctl(self, disks, action="status"): 369 | """ Execute smartctl on listed drive. If no drive selected, run it on all available drive. """ 370 | params = [ "-s", "on", "-d", "sat" ] 371 | if action == "status": 372 | params += [ "-a" ] 373 | elif action == "test": 374 | params += [ "-t", "short" ] 375 | result = [] 376 | progress = xrange(1,len(disks)+1, 1).__iter__() 377 | for disk in disks: 378 | print "\rExecuting smartcl on %s : %3d/%d"%(disk["device"].replace("/dev/rdsk/",""), 379 | progress.next(),len(disks)), 380 | smartparams = params + [ disk["device"]+"p0" ] 381 | result.append(run(smartctl, smartparams)) 382 | print "Done" 383 | return result 384 | 385 | def do_smartcl_getstatus(self, line): 386 | # FIXME : line parsing 387 | if line: 388 | raise NotImplemetedError 389 | else: 390 | disks = self.disks.values() 391 | for (disk, smartoutput) in zip(disks, self.smartctl(disks)): 392 | try: 393 | self._disks[disk["device"]]["smartoutput"] = smartoutput 394 | smartoutput = re.sub("\n[ \t]+", " ", smartoutput) 395 | if "test failed" in smartoutput: 396 | print " Disk %s fail his last test"%disk["device"].replace("/dev/rdsk/", "") 397 | zob= re.findall("(Self-test execution status.*)", smartoutput) 398 | except KeyError: 399 | pass 400 | 401 | def do_smartcl_runtest(self, line): 402 | # FIXME : line parsing 403 | if line: 404 | raise NotImplemetedError 405 | else: 406 | disks = self.disks.values() 407 | self.smartctl(disks, action="test") 408 | 409 | def get_enclosure(self, line): 410 | """ Try to find an enclosure """ 411 | aliases = revert(self.aliases) 412 | if line in aliases: 413 | line = aliases[line] 414 | if line in self.enclosures: 415 | return line 416 | if line.lower() in self.enclosures: 417 | return line.lower() 418 | try: 419 | c, e = line.split(":", 1) 420 | c, e = long(c), long(e) 421 | tmp = [ v["id"].lower() for v in self.enclosures.values() 422 | if v["controller"] == c and v["index"] == e ] 423 | if len(tmp) != 1: raise 424 | return tmp[0] 425 | except Exception, e: 426 | #print e 427 | return None 428 | 429 | def get_disk(self, line): 430 | for t in (line, "/dev/rdsk/%s"%line, line.upper(), line.lower()): 431 | tmp = self._disks.get(t, None) 432 | if tmp: 433 | return [ tmp ] 434 | 435 | # Try to locate by path 436 | try: 437 | # Check if first element of path is an enclosure 438 | tmp = line.split(":",2) 439 | if len(tmp) == 2: 440 | e = self.get_enclosure(tmp[0]) 441 | if e: 442 | return [ disk for disk in self.disks.values() 443 | if disk["enclosure"] == e and disk["slot"] == long(tmp[1]) ] 444 | else: 445 | c, e, s = tmp 446 | c, e, s = long(c), long(e), long(s) 447 | return [ disk for disk in self.disks.values() 448 | if c in disk["controller"] and disk["enclosureindex"] == e 449 | and disk["slot"] == s ] 450 | except Exception, e: 451 | print e 452 | return None 453 | 454 | def complete_enumerate(self, text, line, begidx, endidx): 455 | if line.count(" ") > 1: 456 | # FIXME Only use enclosure with drive attached ... 457 | result = [] 458 | result.extend(self.enclosures.keys()) 459 | result.extend([ "%(controller)s:%(index)s"%e for e in self.enclosures.values() ]) 460 | result.extend(self.aliases.values()) 461 | else: 462 | result = [ "mirror", "raidz1", "raidz2", "raidz3" ] 463 | return [ i for i in result if i.startswith(text) ] 464 | 465 | def do_enumerate(self, line): 466 | """ 467 | Enumerate disks sequentially to help zpool creation. 468 | 469 | Eg : 470 | 2 way mirror on 1 enclosures : 471 | enumerate mirror backplane1 backplane1 472 | mirror disk1_backplane1 disk2_backplane1 mirror disk3_backplane1 disk4_backplane1 [...] 473 | 474 | 2 way mirror on 2 enclosures : 475 | enumerate mirror [backplane1] [backplane2] 476 | output : 477 | mirror disk1_backplane1 disks1_backplane2 mirror disk2_backplane1 disk2_backplane2 [...] 478 | 479 | enumerate raidz2 b1 b2 b1 b2 : 480 | raidz2 d1_b1 d1_b2 d2_b1 d2_b2 raidz2 d3_b1 d3_b2 d4_b1 d4_b2 [...] 481 | """ 482 | line = line.strip() 483 | if not line: return 484 | line = line.split() 485 | text = line.pop(0) 486 | # Get enclosure id for each parameters 487 | enclosures = [ self.get_enclosure(i) for i in line ] 488 | # Then build a list of drives for each enclosure 489 | disks = {} 490 | for enclosure in enclosures: 491 | disks[enclosure] = [ disk for disk in self.disks.values() if disk["enclosure"] == enclosure ] 492 | # And sort it per drive index 493 | disks[enclosure].sort(key=lambda a: a["slot"]) 494 | # Now, iterate on each enclosures we get a print the drive device name 495 | debug = [] 496 | result = [] 497 | while True: 498 | # Use a temporary list so we don't print partial calculation 499 | tmp = [ text ] 500 | debug.append(text) 501 | try: 502 | for enclosure in enclosures: 503 | # Get next disk 504 | disk = disks[enclosure].pop(0) 505 | # Add what we need 506 | tmp.append(disk["device"].replace("/dev/rdsk/","")) 507 | debug.append(disk["path"][0]) 508 | except IndexError: 509 | break 510 | result.extend(tmp) 511 | print "Debug with drive path : " + " ".join(debug) 512 | print "C/C this in your zpool create cmd line : " + " ".join(result) 513 | 514 | def do_configdump(self, path): 515 | if not path: 516 | path = pj(".", "configudump-%s"%hostname) 517 | if not os.path.exists(path): 518 | os.makedirs(path) 519 | tmp = run(sas2ircu, "LIST") 520 | self.discover_controllers(tmp) 521 | file(pj(path, "sas2ircu-list.txt"), "w").write(tmp) 522 | for ctrl in self.controllers: 523 | file(pj(path, "sas2ircu-%s-display.txt"%ctrl), "w").write( 524 | run(sas2ircu, [ctrl, "DISPLAY"])) 525 | file(pj(path, "prtconf-v.txt"), "w").write( 526 | run(prtconf, "-v")) 527 | file(pj(path, "zpool-status.txt"), "w").write( 528 | run(zpool, "status")) 529 | print "Dumped all value to path %s"%path 530 | 531 | def ledparse(self, value, line): 532 | line = line.strip() 533 | targets = [] 534 | if line == "all": 535 | targets = self.disks 536 | else: 537 | # Try to see if it's an enclosure 538 | target = self.get_enclosure(line) 539 | if target: 540 | targets = [ disk for disk in self.disks.values() if disk["enclosure"] == target ] 541 | else: 542 | # Try to see if it's a disk 543 | targets = self.get_disk(line) 544 | if targets: 545 | self.set_leds(targets, value) 546 | else: 547 | print "Could not find what you're talking about" 548 | 549 | def do_ledon(self, line): 550 | """ Turn on locate led on parameters FIXME : syntax parameters""" 551 | self.ledparse(True, line) 552 | 553 | def complete_ledon(self, text, line, begidx, endidx): 554 | candidates = [ "all", "ALL" ] 555 | candidates.extend(self.aliases.values()) 556 | candidates.extend([ disk["device"].replace("/dev/rdsk/", "") for disk in self.disks.values() ]) 557 | candidates.extend([ disk["serial"] for disk in self.disks.values() ]) 558 | candidates.extend([ "%s:%s:%s"%(ctrl, disk["enclosureindex"], disk["slot"]) 559 | for disk in self.disks.values() for ctrl in disk["controller"] ]) 560 | candidates.extend([ "%(controller)s:%(index)s"%enclosure for enclosure in self.enclosures.values() ] ) 561 | candidates.sort() 562 | return [ i for i in candidates if i.startswith(text) ] 563 | 564 | complete_ledoff = complete_ledon 565 | def do_ledoff(self, line): 566 | """ Turn off locate led on parameters FIXME : syntax parameters""" 567 | self.ledparse(False, line) 568 | 569 | def do_alias(self, line): 570 | """ 571 | Used to set a name on a enclosure. 572 | 573 | Usage : alias enclosure name 574 | alias -r name 575 | alias -r enclosure 576 | Without parameters : list current alias 577 | """ 578 | if not line: 579 | pprint(self.aliases) 580 | elif line.startswith("-r"): 581 | junk, alias = line.split(" ",1) 582 | alias = alias.strip() 583 | if alias in self.aliases: 584 | del self.aliases[alias] 585 | else: 586 | # We have to do a reverse lookup to find it ! 587 | tmp = revert(self.aliases) 588 | if alias in tmp: 589 | del self.aliases[tmp[alias]] 590 | self.do_save() 591 | elif " " in line: 592 | target, alias = line.split(" ",1) 593 | alias = alias.strip() 594 | enclosure = self.get_enclosure(target.strip()) 595 | if not enclosure: 596 | print "No such enclosure %s"%target.lower() 597 | else: 598 | self.aliases[enclosure] = alias 599 | self.do_save() 600 | 601 | def complete_alias(self, text, line, begidx, endidx): 602 | if line.startswith("alias -r "): 603 | return ([ i for i in self.aliases.keys() if i.startswith(text) ] + 604 | [ i for i in self.aliases.values() if i.startswith(text) ]) 605 | if line.count(" ") == 1: 606 | result = [] 607 | result.extend(self.enclosures.keys()) 608 | result.extend([ "%(controller)s:%(index)s"%e for e in self.enclosures.values() ]) 609 | return [ i for i in result if i.startswith(text) ] 610 | 611 | def do_mangle(self, junk=""): 612 | """ This function is automatically called when piping something to diskmap. 613 | 614 | It'll suffix all drive name with the enclosure name they are in (defined with an 615 | alias) and the drive slot. 616 | 617 | Try : iostat -x -e -n 1 | diskmap.py 618 | """ 619 | if sys.stdin.isatty(): 620 | print "This command is not intented to be executed in interactive mode" 621 | return 622 | replacelist = [] 623 | for enclosure, alias in self.aliases.items(): 624 | for disk in self.disks.values(): 625 | if disk["enclosure"] == enclosure: 626 | tmp = disk["device"].replace("/dev/rdsk/", "") 627 | replacelist.append((tmp, "%s/%s%02d"%(tmp, alias, disk["slot"]))) 628 | line = sys.stdin.readline() 629 | while line: 630 | for r, e in replacelist: 631 | line = line.replace(r, e) 632 | sys.stdout.write(line) 633 | sys.stdout.flush() 634 | line = sys.stdin.readline() 635 | 636 | def do_sd_timeout(self, timeout=""): 637 | """ 638 | Get / Set sd timeout value 639 | 640 | When no parameter is present, display the current sd_io_time, and check that running 641 | drive use the same timing. 642 | 643 | This script will only change value for the running drive. If you wan't to apply change 644 | permanently, put 'set sd:sd_io_time=5' in /etc/system 645 | 646 | Be aware that the script will change the default value of sd_io_time, and also change 647 | the current value for all drive in your system. 648 | 649 | See : http://blogs.everycity.co.uk/alasdair/2011/05/adjusting-drive-timeouts-with-mdb-on-solaris-or-openindiana/ 650 | """ 651 | if timeout: 652 | try: 653 | timeout = int(timeout) 654 | except: 655 | print "Invalid timeout specified" 656 | return 657 | # Displaying current timeout 658 | tmp = run(mdb, "-k", tosend="sd_io_time::print\n") 659 | globaltimeout = int(tmp.strip(), 16) 660 | print "Current Global sd_io_time : %s"%globaltimeout 661 | drivestimeout = run(mdb, "-k", tosend="::walk sd_state | ::grep '.!=0' | " 662 | "::print -a struct sd_lun un_cmd_timeout\n") 663 | values = [ int(i, 16) for i in re.findall("= (0x[0-9a-f]+)", drivestimeout) if i ] 664 | print "Got %s values from sd disk driver, %s are not equal to system default"%( 665 | len(values), len(values)-values.count(globaltimeout)) 666 | if timeout: # We want to set new timeout for drives 667 | # Set global timeout 668 | print "Setting global timeout ...", 669 | run(mdb, "-kw", tosend="sd_io_time/W 0x%x\n"%timeout) 670 | # Set timeout for every drive 671 | for driveid in re.findall("(.+) un_cmd_timeout", drivestimeout): 672 | print "\rSetting timeout for drive id %s ..."%driveid, 673 | run(mdb, "-kw", tosend="%s/W 0x%x\n"%(driveid, timeout)) 674 | print "Done" 675 | print "Don't forget add to your /etc/system 'set sd:sd_io_time=%s' so change persist accross reboot"%timeout 676 | 677 | def __str__(self): 678 | result = [] 679 | for i in ("controllers", "enclosures", "disks"): 680 | result.append(i.capitalize()) 681 | result.append("="*80) 682 | result.append(pformat(getattr(self,i))) 683 | result.append("") 684 | return "\n".join(result) 685 | 686 | 687 | import unittest 688 | class TestConfigs(unittest.TestCase): 689 | pass 690 | 691 | 692 | 693 | if __name__ == "__main__": 694 | #if not os.path.isfile(sas2ircu): 695 | # sys.exit("Error, cannot find sas2ircu (%s)"%sas2ircu) 696 | sm = SesManager() 697 | if len(sys.argv) > 1: 698 | sm.preloop() 699 | sm.onecmd(" ".join(sys.argv[1:])) 700 | sm.postloop() 701 | elif sys.stdin.isatty(): 702 | sm.cmdloop() 703 | else: 704 | sm.preloop() 705 | sm.onecmd("mangle") 706 | sm.postloop() 707 | 708 | 709 | --------------------------------------------------------------------------------