diff --git a/client/lib/engine/bin/DiskLib.py b/client/lib/engine/bin/DiskLib.py index 81ad6a5..52e33fe 100644 --- a/client/lib/engine/bin/DiskLib.py +++ b/client/lib/engine/bin/DiskLib.py @@ -2,6 +2,8 @@ import filecmp import subprocess import shutil import os +import stat +from pathlib import Path from CacheLib import * from FileSystemLib import * @@ -365,142 +367,187 @@ def ogDevToDisk(dev): ogRaiseError(OG_ERR_NOTFOUND, dev) return OG_ERR_NOTFOUND -def ogDiskToDev(*args): - # Variables locales - CACHEFILE = "/var/cache/disks.cfg" - ALLDISKS = [] - MPATH = VOLGROUPS = ZFSVOLS = DISK = PART = ZPOOL = None - i = 1 - - # Si se solicita, mostrar ayuda. - if len(args) == 1 and args[0] == "help": - ogHelp('ogDiskToDev', 'ogDiskToDev int_ndisk [int_npartition]', - 'ogDiskToDev => /dev/sda /dev/sdb', - 'ogDiskToDev 1 => /dev/sda', - 'ogDiskToDev 1 1 => /dev/sda1') - return - - # Borrar fichero de caché de configuración si hay cambios en las particiones. - if not filecmp.cmp('/proc/partitions', '/tmp/.partitions'): - # Guardar copia de las particiones definidas para comprobar cambios. - shutil.copy2('/proc/partitions', '/tmp/.partitions') - os.remove(CACHEFILE) - - # Si existe una correspondencia con disco/dispositivo en el caché; mostrarlo y salir. - with open(CACHEFILE, 'r') as f: - for line in f: - parts = line.strip().split(':') - if len(parts) == 2 and parts[0] == ":".join(args): - print(parts[1]) - return - - # Continuar para detectar nuevos dispositivos. - # Listar dispositivos de discos. - all_disks = subprocess.getoutput("lsblk -n -e 1,2 -x MAJ:MIN 2>/dev/null || lsblk -n -e 1,2").splitlines() +def _getAllDisks(): + ret = [] + all_disks = subprocess.run("lsblk -n -e 1,2 -x MAJ:MIN 2>/dev/null || lsblk -n -e 1,2", shell=True, capture_output=True, text=True).stdout.splitlines() for line in all_disks: parts = line.split() if parts[5] == "disk": - ALLDISKS.append(f"/dev/{parts[0]}") + parts[0].replace ('!', '/') + ret.append(f"/dev/{parts[0]}") + return ret - # Listar volúmenes lógicos. - VOLGROUPS = subprocess.getoutput("vgs -a --noheadings 2>/dev/null").splitlines() - VOLGROUPS = [f"/dev/{line.split()[0]}" for line in VOLGROUPS] +def _getAllVolGroups(): + vgs = subprocess.run(['vgs', '-a', '--noheadings'], capture_output=True, text=True).stdout.splitlines() + ret = [f"/dev/{line.split()[0]}" for line in vgs] + return ret - # Detectar caminos múltiples (ignorar mensaje si no está configurado Multipath). +def _getMPath(): + ret = alldisks2remove = [] try: - MPATH = subprocess.getoutput("multipath -l -v 1 2>/dev/null").splitlines() - MPATH = [f"/dev/mapper/{line.split()[0]}" for line in MPATH] - # Quitar de la lista los discos que forman parte de Multipath. + mpath = subprocess.run(['multipath', '-l', '-v', '1'], capture_output=True, text=True).stdout.splitlines() + ret = [f"/dev/mapper/{line.split()[0]}" for line in mpath] for line in subprocess.getoutput("multipath -ll").splitlines(): if line.split()[5] == "ready": - disk = f"/dev/{line.split()[2]}" - if disk in ALLDISKS: - ALLDISKS.remove(disk) + alldisks2remove.append (f"/dev/{line.split()[2]}") + except FileNotFoundError: + pass except subprocess.CalledProcessError: - MPATH = [] + pass + return ret, alldisks2remove - # Detectar volúmenes ZFS. - ZFSVOLS = subprocess.getoutput("blkid").splitlines() - ZFSVOLS = [line.split(":")[0] for line in ZFSVOLS if "zfs" in line] +def _getAllZFSVols(): + zfsvols = subprocess.run(['blkid'], capture_output=True, text=True).stdout.splitlines() + return [line.split(":")[0] for line in zfsvols if "zfs" in line] - # Mostrar salidas segun el número de parametros. - if len(args) == 0: - print(" ".join(ALLDISKS)) - elif len(args) == 1: - # Error si el parámetro no es un número positivo. - if not args[0].isdigit() or int(args[0]) <= 0: - ogRaiseError(OG_ERR_FORMAT, args[0]) +#/** +# ogDiskToDev [int_ndisk [int_npartition]] +#@brief Devuelve la equivalencia entre el nº de orden del dispositivo (dicso o partición) y el nombre de fichero de dispositivo correspondiente. +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return Para 0 parametros: Devuelve los nombres de ficheros de los dispositivos sata/ata/usb linux encontrados. +#@return Para 1 parametros: Devuelve la ruta del disco duro indicado. +#@return Para 2 parametros: Devuelve la ruta de la particion indicada. +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Dispositivo no detectado. +#*/ ## +def ogDiskToDev (arg_disk=None, arg_part=None): + CACHEFILE = "/var/cache/disks.cfg" + + try: + if arg_part != None: + arg_part = int (arg_part) + except: + ogRaiseError([], ogGlobals.OG_ERR_FORMAT, f"{arg_disk} {arg_part}") + return + + try: + if arg_disk != None: + arg_disk = int (arg_disk) + except: + ogRaiseError([], ogGlobals.OG_ERR_FORMAT, arg_disk) + return + + # Si se solicita, mostrar ayuda. + #if len(args) == 1 and args[0] == "help": + # ogHelp('ogDiskToDev', 'ogDiskToDev int_ndisk [int_npartition]', + # 'ogDiskToDev => /dev/sda /dev/sdb', + # 'ogDiskToDev 1 => /dev/sda', + # 'ogDiskToDev 1 1 => /dev/sda1') + # return + + # Borrar fichero de caché de configuración si hay cambios en las particiones. + proc_partitions = Path ('/proc/partitions').read_text() + tmp_partitions = Path ('/tmp/.partitions').read_text() if os.path.exists ('/tmp/.partitions') else '' + if proc_partitions != tmp_partitions: + # Guardar copia de las particiones definidas para comprobar cambios. + shutil.copy2('/proc/partitions', '/tmp/.partitions') + try: + ogEcho ([], 'info', f'removing cachefile') + os.remove(CACHEFILE) + except FileNotFoundError: + pass + + # Si existe una correspondencia con disco/dispositivo en el caché; mostrarlo y salir. + if arg_disk and os.path.exists (CACHEFILE): + with open(CACHEFILE, 'r') as f: + args_joined = ' '.join (map (str, filter (None, [arg_disk,arg_part]))) + ogEcho ([], 'info', f'args_joined ({args_joined})') + for line in f: + parts = line.strip().split(':') + if len(parts) == 2 and parts[0] == args_joined: + return parts[1] + + # Continuar para detectar nuevos dispositivos. + + ALLDISKS = _getAllDisks() + + VOLGROUPS = _getAllVolGroups() + ALLDISKS += VOLGROUPS + + MPATH, ALLDISKS_to_remove =_getMPath() + ALLDISKS = list (set (ALLDISKS) - set (ALLDISKS_to_remove)) + + ZFSVOLS = _getAllZFSVols() + ALLDISKS += ZFSVOLS + print (f'ALLDISKS ({ALLDISKS}) VOLGROUPS ({VOLGROUPS}) MPATH ({MPATH}) ALLDISKS_to_remove ({ALLDISKS_to_remove}) ZFSVOLS ({ZFSVOLS})') + + # No params: return all disks + if arg_disk is None: + return " ".join(ALLDISKS) + + # arg_disk is set: return it + if arg_part is None: + if arg_disk > len (ALLDISKS): + ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_disk) return - disk = ALLDISKS[int(args[0])-1] - # Error si el fichero no existe. + disk = ALLDISKS[arg_disk-1] if not os.path.exists(disk): - ogRaiseError(OG_ERR_NOTFOUND, args[0]) + ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_disk) return # Actualizar caché de configuración y mostrar dispositivo. with open(CACHEFILE, 'a') as f: - f.write(f"{args[0]}:{disk}\n") - print(disk) - elif len(args) == 2: - # Error si los 2 parámetros no son números positivos. - if not args[0].isdigit() or int(args[0]) <= 0 or not args[1].isdigit() or int(args[1]) <= 0: - ogRaiseError(OG_ERR_FORMAT, f"{args[0]} {args[1]}") - return - disk = ALLDISKS[int(args[0])-1] - # Error si el fichero no existe. - if not os.path.exists(disk): - ogRaiseError(OG_ERR_NOTFOUND, args[0]) - return - part = f"{disk}{args[1]}" - # Comprobar si es partición. - if os.path.exists(part): - # Actualizar caché de configuración y mostrar dispositivo. - with open(CACHEFILE, 'a') as f: - f.write(f"{args[0]} {args[1]}:{part}\n") - print(part) - else: - # Comprobar si RAID o Multipath (tener en cuenta enlace simbólico). - part = f"{disk}p{args[1]}" - if os.path.exists(part) and os.stat(part, follow_symlinks=True).st_mode[0] == "b": - # Actualizar caché de configuración y mostrar dispositivo. - with open(CACHEFILE, 'a') as f: - f.write(f"{args[0]} {args[1]}:{part}\n") - print(part) - else: - part = "" - # Comprobar si volumen lógico. - if disk in VOLGROUPS: - lvscan = subprocess.getoutput("lvscan -a 2>/dev/null").splitlines() - for line in lvscan: - parts = line.split("'") - if parts[1].startswith(f"{disk}/") and i == int(args[1]): - part = parts[1] - break - i += 1 - # Comprobar si volumen ZFS que puede ser montado. - if disk in ZFSVOLS: - subprocess.run(["zpool", "import", "-f", "-R", "/mnt", "-N", "-a"], stderr=subprocess.DEVNULL) - zpool = subprocess.getoutput(f"blkid -s LABEL -o value {disk}") - zfs_list = subprocess.getoutput(f"zfs list -Hp -o name,canmount,mountpoint -r {zpool}").splitlines() - for line in zfs_list: - parts = line.split() - if parts[1] == "on" and parts[2] != "none": - if i == int(args[1]): - part = parts[0] - break - i += 1 - # Salir si no se encuentra dispositivo. - if not part: - ogRaiseError(OG_ERR_NOTFOUND, f"{args[0]} {args[1]}") - return - # Devolver camino al dispositivo. - # Actualizar caché de configuración y mostrar dispositivo. - with open(CACHEFILE, 'a') as f: - f.write(f"{args[0]} {args[1]}:{part}\n") - print(part) - else: - ogRaiseError(OG_ERR_FORMAT) + f.write(f"{arg_disk}:{disk}\n") + return disk + + # arg_disk and arg_part are set: there are several possibilities + disk = ALLDISKS[arg_disk-1] + if not os.path.exists(disk): + ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_disk) return + # Plain partition + part = f"{disk}{arg_part}" + if os.path.exists(part): + # Actualizar caché de configuración y mostrar dispositivo. + with open(CACHEFILE, 'a') as f: + f.write(f"{arg_disk} {arg_part}:{part}\n") + return part + + # RAID/Multipath (tener en cuenta enlace simbólico). + part = f"{disk}p{arg_part}" + if os.path.exists(part) and stat.S_ISBLK (os.stat (part, follow_symlinks=True).st_mode): + # Actualizar caché de configuración y mostrar dispositivo. + with open(CACHEFILE, 'a') as f: + f.write(f"{arg_disk} {arg_part}:{part}\n") + return part + + part = "" + # Logical volume + if disk in VOLGROUPS: + lvscan = subprocess.run (['lvscan', '-a'], capture_output=True, text=True).stdout.splitlines() + i = 0 + for line in lvscan: + parts = line.split("'") + if parts[1].startswith(f"{disk}/") and i == arg_part: + part = parts[1] + break + i += 1 + + # ZFS volume + if disk in ZFSVOLS: + subprocess.run(["zpool", "import", "-f", "-R", "/mnt", "-N", "-a"]) + zpool = subprocess.run(['blkid', '-s', 'LABEL', '-o', 'value', disk], capture_output=True, text=True).stdout + zfs_list = subprocess.run(['zfs', 'list', '-Hp', '-o', 'name,canmount,mountpoint', '-r', zpool], capture_output=True, text=True).stdout.splitlines() + i = 0 + for line in zfs_list: + parts = line.split() + if parts[1] == "on" and parts[2] != "none": + if i == int(args[1]): + part = parts[0] + break + i += 1 + + if not part: + ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, f"{arg_disk} {arg_part}") + return + + # Actualizar caché de configuración y mostrar dispositivo. + with open(CACHEFILE, 'a') as f: + f.write(f"{arg_disk} {arg_part}:{part}\n") + + return part + def ogGetDiskSize(*args): # Variables locales DISK = SIZE = None @@ -1365,4 +1412,4 @@ def ogUnlockDisk(*args): def ogUpdatePartitionTable(): for disk in ogDiskToDev(): - subprocess.run(["partprobe", disk]) \ No newline at end of file + subprocess.run(["partprobe", disk]) diff --git a/client/lib/engine/bin/FileSystemLib.py b/client/lib/engine/bin/FileSystemLib.py index 33d6c8c..7322c67 100644 --- a/client/lib/engine/bin/FileSystemLib.py +++ b/client/lib/engine/bin/FileSystemLib.py @@ -828,4 +828,4 @@ def ogGetFreeSize(int_ndisk, int_npartition, str_SizeOutput): used = float(output[2]) * factor free = float(output[3]) * factor - return free \ No newline at end of file + return free