├── README.md └── lsidrivemap /README.md: -------------------------------------------------------------------------------- 1 | lsidrivemap 2 | =========== 3 | 4 | 5 | Owners of a IBM M1015 / LSI 9220-8i controller can use this utility to map 6 | controller ports to drives. 7 | 8 | I have a 24 bay disk chassis and use it to create a physical map of which 9 | drive is in which slot. 10 | 11 | ![nas][nas] 12 | 13 | [nas]: http://louwrentius.com/static/images/zfsnas01.jpg 14 | 15 | 16 | The tool can also show the temperature of the drive. 17 | 18 | Example output: 19 | 20 | root@nano:~# lsidrivemap temp 21 | 22 | | 37 | 40 | 40 | 37 | 23 | | 36 | 36 | 37 | 36 | 24 | | 35 | 37 | 36 | 36 | 25 | | 35 | 37 | 36 | 35 | 26 | | 35 | 36 | 37 | 36 | 27 | | 34 | 35 | 36 | 35 | 28 | 29 | root@nano:~# lsidrivemap disk 30 | 31 | | sdr | sds | sdt | sdq | 32 | | sdu | sdv | sdx | sdw | 33 | | sdi | sdl | sdaa | sdm | 34 | | sdj | sdk | sdn | sdo | 35 | | sdb | sdc | sde | sdf | 36 | | sda | sdd | sdh | sdg | 37 | 38 | root@nano:~# lsidrivemap wwn 39 | 40 | | 5000cca23dc53843 | 5000cca23dc52fea | 5000cca23dc31656 | 5000cca23dc01655 | 41 | | 5000cca23dc459ee | 5000cca22bf0f4c3 | 5000cca22bef486a | 5000cca23dc51764 | 42 | | 5000cca23dc186cf | 5000cca23dc02062 | 5000cca23dda5a33 | 5000cca23dd398fa | 43 | | 5000cca23dd56dfb | 5000cca23dd3a8cd | 5000cca23dd9b7df | 5000cca23dda6ae9 | 44 | | 5000cca23dd04ded | 5000cca23dd54779 | 5000cca23dd59e65 | 5000cca23dd59b65 | 45 | | 5000cca23dd45619 | 5000cca23dd57131 | 5000cca23dd329ba | 5000cca23dd4f9d6 | 46 | 47 | The wwn name of a drive is found in /dev/disk/by-id/ 48 | 49 | Customization 50 | ------------- 51 | 52 | The output is based on my 24 bay drive chassis that has 53 | six rows of four drives. You may need to customise the 54 | 'print_controller' function to suit your own needs. 55 | 56 | Known Issues 57 | ------------ 58 | The script reads the WWN serial from the drive and uses 59 | it to find the drive name in /dev/disk/by-id. If the megacli 60 | command does not return a WWN, which happens on older WD drives 61 | for me, no data is returned. 62 | 63 | Requirements 64 | ------------ 65 | - The script requires Python 2.7 or higher. 66 | - LSI command line utility megacli or megacli64 (google for a download) 67 | - put the /opt/MegaRAID/MegaCli/ directory in your path and either create 68 | a symbolic link to MegaCli or MegaCli64 with the name of 'megacli' in that folder. 69 | 70 | 71 | -------------------------------------------------------------------------------- /lsidrivemap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | import subprocess 5 | import re 6 | 7 | portspercontroller = 8 8 | 9 | datatype = sys.argv[1] 10 | 11 | 12 | def number_of_controllers(): 13 | rawdata = subprocess.Popen(['/opt/MegaRAID/MegaCli/megacli', '-cfgdsply', 14 | '-aALL'], stdout=subprocess.PIPE, 15 | stderr=subprocess.PIPE).communicate()[0] 16 | regex = re.compile('Adapter:.*') 17 | match = regex.findall(rawdata) 18 | return len(match) 19 | 20 | 21 | def get_drive_wwn(controller, port): 22 | rawdata = subprocess.Popen(['/opt/MegaRAID/MegaCli/megacli', '-pdinfo', 23 | '-physdrv', '[64:' + str(port) + ']', 24 | '-a' + str(controller)], stdout=subprocess.PIPE, 25 | stderr=subprocess.PIPE).communicate() 26 | regex = re.compile('WWN: (.*)') 27 | match = regex.search(rawdata[0]) 28 | try: 29 | return match.group(1) 30 | except(AttributeError): 31 | return "" 32 | 33 | 34 | def get_all_disk_by_id(): 35 | disk_by_id_data = subprocess.Popen(['ls', '-alh', '/dev/disk/by-id'], 36 | stdout=subprocess.PIPE, 37 | stderr=subprocess.PIPE).communicate()[0] 38 | return disk_by_id_data 39 | 40 | 41 | def convert_wwn_to_drivename(wwn, diskbyid): 42 | regex = re.compile(wwn + '(.*)') 43 | match = regex.search(diskbyid) 44 | try: 45 | return match.group(1).split("/")[2] 46 | except(AttributeError): 47 | return "" 48 | except(IndexError): 49 | return "" 50 | 51 | 52 | def get_drive_temp(controller, port): 53 | 54 | rawdata = subprocess.Popen(['/opt/MegaRAID/MegaCli/megacli', '-pdinfo', 55 | '-physdrv', '[64:' + str(port) + ']', 56 | '-a' + str(controller)], stdout=subprocess.PIPE, 57 | stderr=subprocess.PIPE).communicate()[0] 58 | 59 | regex = re.compile('Drive Temperature :(.*)') 60 | match = regex.search(rawdata) 61 | try: 62 | temp = match.group(1).split("C")[0] 63 | 64 | # 65 | # Ugly hack: issue with some old WD drives 66 | # Controller reports 65C for them. 67 | # 68 | if int(temp) >= 60: 69 | temp = "?" 70 | return temp 71 | 72 | except(AttributeError): 73 | return "" 74 | except(IndexError): 75 | return "" 76 | 77 | 78 | def get_drive_name(controller, port): 79 | wwn = get_drive_wwn(controller, port) 80 | diskbyid = get_all_disk_by_id() 81 | drivename = convert_wwn_to_drivename(wwn, diskbyid) 82 | return drivename 83 | 84 | 85 | def get_drive_info(controller, port, datatype): 86 | if datatype == "disk": 87 | return get_drive_name(controller, port) 88 | if datatype == "temp": 89 | return get_drive_temp(controller, port) 90 | if datatype == "wwn": 91 | return get_drive_wwn(controller, port) 92 | 93 | 94 | def fetch_data(datatype): 95 | 96 | drivearray =\ 97 | [[0 for x in xrange(portspercontroller)] 98 | for x in xrange(controllercount)] 99 | 100 | for x in xrange(controllercount): 101 | for y in xrange(portspercontroller): 102 | disk = get_drive_info(x, y, datatype) 103 | if len(disk) == 0: 104 | disk = "" 105 | drivearray[x][y] = disk 106 | #temp = get_drive_temp(x,y) 107 | #print str(x) + " " + str(y) + "Temp: " + str(temp) 108 | 109 | return drivearray 110 | 111 | 112 | def get_largest_width(array): 113 | width = 0 114 | for controller in array: 115 | for port in controller: 116 | size = len(port) 117 | if size > width: 118 | width = size 119 | return width 120 | 121 | 122 | def print_controller(controller, array): 123 | 124 | width = str(get_largest_width(array)) 125 | c = array[controller] 126 | top = [c[3], c[2], c[1], c[0]] 127 | bottom = [c[7], c[6], c[5], c[4]] 128 | print "| {:^{width}} | {:^{width}} | {:^{width}} | {:^{width}} |".format( 129 | *top, width=width) 130 | print "| {:^{width}} | {:^{width}} | {:^{width}} | {:^{width}} |".format( 131 | *bottom, width=width) 132 | 133 | 134 | if __name__ == "__main__": 135 | 136 | controllercount = number_of_controllers() 137 | data = fetch_data(datatype) 138 | 139 | print 140 | for x in reversed(xrange(controllercount)): 141 | print_controller(x, data) 142 | print 143 | --------------------------------------------------------------------------------