#!/usr/bin/env python3 """ Mark6 utility m6sg_mount v2.3 Usage: m6sg_mount [-u] By default mounts all disks of the connected Mark6 modules. With option '-u' all disks of the Mark6 modules are unmounted. The main purpose of this script is to avoid having to start cplane, then dplane, and then send the necessary da-client commands. Optional config file /etc/default/mark6_slots_pci Each line should have one SAS controller reference (e.g., pci-0000:0a:00.0). The order guides the assignment to incremental Mark6 slots. Controller with pci-... on the 1st line will be used for slots 1&2, controller with pci-... on the 2nd line will be used for slots 3&4, and so on. You can find the SAS controller(s) on your system with a series of $ udevadm info --query=all --name=/dev/sda | grep ID_SAS_PATH ID_SAS_PATH=pci-0000:0a:00.0-sas-phy3-lun-0 """ import pyudev, re, subprocess, sys MARK6_ROOT = '/mnt/disks/%d/%d' MARK6_META = '/mnt/disks/.meta/%d/%d' CHECK_RESULTS = False # specifies whether to check results from subprocess() launches SLOTS_FILE = '/etc/default/mark6_slots_pci' # Changelog: # xx/2015 v1.0 Jan Wagner, basic version for quick (un)mounting # xx/2015 v1.1 Harro Verkouter, improvements to preseve consistent mount points # 11/2015 v2.0 Jan Wagner, updated to use pyudev instead of 3rd party 'lsscsi' # 05/2017 v2.1 Jan Wagner, don't depend on installed disks to determine number of SAS cards installed # 05/2022 v2.2 Jan Wagner, add support for multiple expansion chassis but still just max 16 disks per SAS controller card # 06/2022 v2.3 Jan Wagner, read /etc/default/mark6_slots_pci containing SAS controller PCI bus roots in order of slot-pairs # Helpful to add to sudoers: # %oper ALL=(root) NOPASSWD:/bin/mount # %oper ALL=(root) NOPASSWD:/bin/umount # %oper ALL=(root) NOPASSWD:/bin/chmod # %oper ALL=(root) NOPASSWD:/bin/chown MAX_DISKS_PER_SAS = 16 def sort_MPT2SAS_controller_list(ctlist): ''' Sort a list of SAS controller PCI bus roots such as ['pci-0000:0a:00.0','pci-0000:08:00.0','pci-0000:09:00.0']. The sorting order is made to follow the order of occurrence in a user config file - if available. ''' with open(SLOTS_FILE) as cfg: user_order = [entry.strip() for entry in cfg.readlines() if len(entry.strip())>0] if len(user_order) > 0: sorted = [] for entry in user_order: if 'pci' not in entry: print('Warning: entry %s does not look legit, the expected format is e.g. pci-0000:0a:00.0.' % (entry)) if entry in ctlist: sorted.append(entry) ctlist.remove(entry) ctlist = sorted + ctlist #if True: # print('Suggestion for %s:' % (SLOTS_FILE)) # for ctlId in ctlist: # print('host%d' % (ctlId)) return ctlist def list_MPT2SAS_disks(): list = [] context = pyudev.Context() ctrl_ids = [] # Find block devices and their controllers for dev in context.list_devices(subsystem='block'): pdev = dev.find_parent('pci') if (pdev == None): continue if pdev.driver in ['mpt2sas','mpt3sas']: # DEVPATH /sys/devices/pci0000:00/0000:00:02.0/0000:06:00.0/host0/port-0:0/end_device-0:0/target0:0:0/0:0:0:0/block/sdb # Controllers may have target0:0:0, target1:0:0, target17:0:0, ... depending perhaps on PCIe slot (0, 1, 17, ...) # Since target numbers n are not known in advance we don't use , but replace it with an index (0,1,2,...) # # Major problem after testing, turns out the target number for the exactly same physical disk # keeps *changing* when the module is keyed off and then on again, especially when other modules are in the system, # i.e. the target does apparently not at all reflect a physical link index. # # Other option: # ID_SAS_PATH=pci-0000:0a:00.0-sas-phy0-lun-0 # The PCI dev root uniquely identifies the SAS controller, consistent over reboots. # The phy consistently identifies the physical link on the SAS controller, consistent over module key off/on cycles. # The phy<0-7> are always on the same module albeit which LED they correspond to is a (at least consistent) mess; phy2 always LED 4 from top. # print(dev, dev['SUBSYSTEM'], dev['DEVPATH'], dev['ID_SAS_PATH']) dsas = dev['ID_SAS_PATH'] droot = re.search('pci-[0-9a-f]*:[0-9a-f]*:[0-9a-f]*.[0-9a-f]',dsas).group(0) if droot not in ctrl_ids: ctrl_ids.append(droot) # Sort the list according to matching entries in a config file if available ctrl_ids = sort_MPT2SAS_controller_list(ctrl_ids) # Now find partitions on disks behind controllers for dev in context.list_devices(subsystem='block', DEVTYPE='partition'): pdev = dev.find_parent('pci') if pdev.driver in ['mpt2sas','mpt3sas']: entry = {} entry['fs'] = dev.get('ID_FS_TYPE') entry['diskmodel'] = dev.get('ID_MODEL') entry['diskserial'] = dev.get('ID_SERIAL') dpath = dev['DEVPATH'] dname = dev['DEVNAME'] # same as in property dev.device_node dpartition = dname[-1:] # Determine SAS controller index from PCI, physical endpoint device index from phy dsas = dev['ID_SAS_PATH'] h = re.search('pci-[0-9a-f]*:[0-9a-f]*:[0-9a-f]*.[0-9a-f]',dsas).group(0) m = ctrl_ids.index(h) phy = re.search('phy[0-9]*',dsas).group(0) t = int(phy[3:]) if t >= MAX_DISKS_PER_SAS: print('Warning: unexpected target nr %d outside 0-%d! Changing it to %d!' % (t,MAX_DISKS_PER_SAS-1,t % MAX_DISKS_PER_SAS)) t = t % MAX_DISKS_PER_SAS # print(pdev.driver, dname, dsas, h, 'ctrl:%d'%(m), 'endpoint:%d'%(t), dpath) # Map the controller and disk device indices to a "slot" and "disk" entry['blkdev'] = dname entry['sas_path'] = dsas entry['mk6_module'] = t//8 + 2*m +1 # 1-based count entry['mk6_slot'] = t % 8 # 0-based count if dpartition=='1': entry['mk6_mountpoint'] = MARK6_ROOT % (entry['mk6_module'],entry['mk6_slot']) elif dpartition=='2': entry['mk6_mountpoint'] = MARK6_META % (entry['mk6_module'],entry['mk6_slot']) else: print('Warning: no partion 1 or 2 found in devpath string %s' % (dpath)) entry['mk6_mountpoint'] = None # print(dsas, h, m, t, dpartition, entry['blkdev'], entry['mk6_module'], entry['mk6_slot'], entry['mk6_mountpoint']) list.append(entry) list = sorted(list, key=lambda k: k['mk6_mountpoint']) return list def mount(dev,dir): print('Mounting %s to %s' % (dev,dir)) p = [] p.append( subprocess.Popen(["/usr/bin/sudo", "/bin/mount", dev, dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) ) p.append( subprocess.Popen(["/usr/bin/sudo", "/bin/chmod", "775", dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) ) p.append( subprocess.Popen(["/usr/bin/sudo", "/bin/chown", "oper:mark6", dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) ) return p def unmount(dir): print('Unmounting %s' % (dir)) p = subprocess.Popen(["/usr/bin/sudo", "/bin/umount", dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) return [p] def mount_all(): procs = [] for entry in list_MPT2SAS_disks(): procs = procs + mount(entry['blkdev'], entry['mk6_mountpoint']) # print('mount %s from %s(%s) to %s' % (entry['sas_path'], entry['blkdev'], entry['blkdev'], entry['mk6_mountpoint'])) return procs def unmount_all(): procs = [] for entry in list_MPT2SAS_disks(): procs = procs + unmount(entry['mk6_mountpoint']) return procs """Unmount any no longer present devices that Linux (bug?) still keeps mounted somewhere""" def unmount_invisible(): query = subprocess.Popen(["/usr/bin/sudo", "/bin/mount"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out,rc) = query.communicate() allmounts = str(out).split('\\n') p = [] for entry in allmounts: if "/mnt/disks/" in entry: dev = entry.split()[0] print('Unmounting',dev) p.append( subprocess.Popen(["/usr/bin/sudo", "/bin/umount", dev], stdout=subprocess.PIPE, stderr=subprocess.PIPE) ) for sp in p: (o,rc) = sp.communicate() def main(argv=sys.argv): if (not(len(argv) in [1,2]) or (len(argv)==2 and argv[1]!='-u')): print(__doc__) sys.exit(-1) if len(argv)==2 and argv[1]=='-u': procs = unmount_all() unmount_invisible() else: procs = mount_all() if CHECK_RESULTS: for p in procs: (o,rc) = p.communicate() if len(rc)>0: print(rc) if __name__ == "__main__": main(sys.argv)