Skip to content

Commit

Permalink
fix MINI 3.0 X (vers 1.0) drive mapping (#14657)
Browse files Browse the repository at this point in the history
  • Loading branch information
yocalebo authored Oct 11, 2024
1 parent 897cf08 commit 9993ac1
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 40 deletions.
39 changes: 12 additions & 27 deletions src/middlewared/middlewared/plugins/enclosure_/enclosure2.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from middlewared.utils import filter_list

from .constants import SUPPORTS_IDENTIFY_KEY
from .enums import ControllerModels, JbofModels
from .enums import JbofModels
from .fseries_drive_identify import set_slot_status as fseries_set_slot_status
from .jbof_enclosures import map_jbof, set_slot_status as _jbof_set_slot_status
from .map2 import combine_enclosures
Expand All @@ -26,29 +26,17 @@ class Config:
cli_namespace = 'storage.enclosure2'
private = True

def get_ses_enclosures(self, dmi=None):
def get_ses_enclosures(self):
"""This generates the "raw" list of enclosures detected on the system. It
serves as the "entry" point to "enclosure2.query" and is foundational in
how all of the structuring of the final data object is returned.
We use pyudev to enumerate enclosure type devices using a socket to the
udev database. While we're at it, we also add some useful keys to the
object (/dev/bsg, /dev/sg, and dmi). Then we use SCSI commands (issued
directly to the enclosure) to generate an object of all elements and the
information associated to each element.
It's _VERY_ important to understand that the "dmi" key is the hingepoint for
identifying what platform we're on. This is SMBIOS data and is burned into
the motherboard before we ship to our customers. This is also how we map the
enclosure's array device slots (disk drives) to a human friendly format.
The `Enclosure` class is where all the magic happens wrt to taking in all the
raw data and formatting it into a structured object that will be consumed by
the webUI team as well as on the backend (alerts, drive identifiction, etc).
how all of the structuring of the final data object is returned. We use
SCSI commands (issued directly to the enclosure) to generate an object of
all elements and the information associated to each element. The `Enclosure`
class is where all the magic happens wrt to taking in all the raw data and
formatting it into a structured object that will be consumed by the webUI
team as well as on the backend (alerts, drive identifiction, etc).
"""
if dmi is None:
dmi = self.middleware.call_sync('system.dmidecode_info')['system-product-name']
return get_ses_enclosures(dmi)
return get_ses_enclosures()

async def map_jbof(self, jbof_qry=None):
"""This method serves as an endpoint to easily be able to test
Expand All @@ -59,14 +47,12 @@ async def map_jbof(self, jbof_qry=None):
jbof_qry = await self.middleware.call('jbof.query')
return await map_jbof(jbof_qry)

def map_nvme(self, dmi=None):
def map_nvme(self):
"""This method serves as an endpoint to easily be able to test
the nvme mapping logic specifically without having to call enclosure2.query
which includes the head-unit and all attached JBODs.
"""
if dmi is None:
dmi = self.middleware.call_sync('system.dmidecode_info')['system-product-name']
return map_nvme(dmi)
return map_nvme()

def get_original_disk_slot(self, slot, enc_info):
"""Get the original slot based on the `slot` passed to us via the end-user.
Expand Down Expand Up @@ -150,8 +136,7 @@ def query(self, filters, options):
label['encid']: label['label']
for label in self.middleware.call_sync('datastore.query', 'truenas.enclosurelabel')
}
dmi = self.middleware.call_sync('system.dmidecode_info')['system-product-name']
for i in self.get_ses_enclosures(dmi) + self.map_nvme(dmi) + self.middleware.call_sync('enclosure2.map_jbof'):
for i in self.get_ses_enclosures() + self.map_nvme() + self.middleware.call_sync('enclosure2.map_jbof'):
if i.pop('should_ignore'):
continue

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from middlewared.utils.scsi_generic import inquiry

from ixhardware import parse_dmi
from .constants import (
MINI_MODEL_BASE,
MINIR_MODEL_BASE,
Expand All @@ -29,8 +30,9 @@


class Enclosure:
def __init__(self, bsg, sg, dmi, enc_stat):
self.bsg, self.sg, self.pci, self.dmi = bsg, sg, bsg.removeprefix('/dev/bsg/'), dmi
def __init__(self, bsg, sg, enc_stat):
self.dmi = parse_dmi()
self.bsg, self.sg, self.pci, = bsg, sg, bsg.removeprefix('/dev/bsg/')
self.encid, self.status = enc_stat['id'], list(enc_stat['status'])
self.vendor, self.product, self.revision, self.encname = self._get_vendor_product_revision_and_encname()
self._get_model_and_controller()
Expand All @@ -48,7 +50,7 @@ def asdict(self):
'name': self.encname, # vendor, product and revision joined by whitespace
'model': self.model, # M60, F100, MINI-R, etc
'controller': self.controller, # if True, represents the "head-unit"
'dmi': self.dmi, # comes from system.dmidecode_info[system-product-name]
'dmi': self.dmi.system_product_name,
'status': self.status, # the overall status reported by the enclosure
'id': self.encid,
'vendor': self.vendor, # t10 vendor from INQUIRY
Expand Down Expand Up @@ -118,7 +120,8 @@ def _get_model_and_controller(self):
2. We check the t10 vendor and product strings returned from the enclosure
using a standard inquiry command
"""
model = self.dmi.removeprefix('TRUENAS-').removeprefix('FREENAS-')
spn = self.dmi.system_product_name
model = spn.removeprefix('TRUENAS-').removeprefix('FREENAS-')
model = model.removesuffix('-HA').removesuffix('-S')
try:
dmi_model = ControllerModels[model]
Expand All @@ -134,7 +137,7 @@ def _get_model_and_controller(self):
except ValueError:
# this shouldn't ever happen because the instantiator of this class
# checks DMI before we even get here but better safe than sorry
logger.warning('Unexpected model: %r from dmi: %r', model, self.dmi)
logger.warning('Unexpected model: %r from dmi: %r', model, spn)
self.model = ''
self.controller = False
return
Expand Down Expand Up @@ -234,7 +237,7 @@ def _get_array_device_mapping_info(self):
vers_key = 'DEFAULT'
if not mapped_info['any_version']:
for key, vers in mapped_info['versions'].items():
if self.revision == key:
if self.dmi.system_version == key:
vers_key = vers
break

Expand Down
6 changes: 4 additions & 2 deletions src/middlewared/middlewared/plugins/enclosure_/nvme2.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from pyudev import Context, Devices, DeviceNotFoundAtPathError

from ixhardware import parse_dmi
from .constants import (
DISK_FRONT_KEY,
DISK_REAR_KEY,
Expand Down Expand Up @@ -251,8 +252,9 @@ def map_r30_or_fseries(model, ctx):
return fake_nvme_enclosure(model, num_of_nvme_slots, mapped, ui_info)


def map_nvme(dmi):
model = dmi.removeprefix('TRUENAS-').removesuffix('-S').removesuffix('-HA')
def map_nvme():
model = parse_dmi().system_product_name.removeprefix('TRUENAS-')
model = model.removesuffix('-S').removesuffix('-HA')
ctx = Context()
if model in (
ControllerModels.R50.value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ def get_ses_enclosure_status(bsg_path):
logger.error('Error querying enclosure status for %r', bsg_path, exc_info=True)


def get_ses_enclosures(dmi, asdict=True):
def get_ses_enclosures(asdict=True):
rv = list()
with suppress(FileNotFoundError):
for i in Path('/sys/class/enclosure').iterdir():
bsg = f'/dev/bsg/{i.name}'
if (status := get_ses_enclosure_status(bsg)):
sg = next((i / 'device/scsi_generic').iterdir())
enc = Enclosure(bsg, f'/dev/{sg.name}', dmi, status)
enc = Enclosure(bsg, f'/dev/{sg.name}', status)
if asdict:
rv.append(enc.asdict())
else:
Expand Down
18 changes: 16 additions & 2 deletions src/middlewared/middlewared/plugins/enclosure_/slot_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,9 +534,8 @@ def get_slot_info(enc):
}
elif enc.model == ControllerModels.MINI3X.value:
return {
'any_version': True,
'any_version': False,
'versions': {
# NOTE: 1.0 "version" has same mapping?? (CORE is the same)
'DEFAULT': {
'id': {
'3000000000000001': {
Expand All @@ -549,6 +548,21 @@ def get_slot_info(enc):
4: {SYSFS_SLOT_KEY: 5, MAPPED_SLOT_KEY: 7, SUPPORTS_IDENTIFY_KEY: False}
}
}
},
'1.0': {
'id': {
'3000000000000002': {
1: {SYSFS_SLOT_KEY: 0, MAPPED_SLOT_KEY: 1, SUPPORTS_IDENTIFY_KEY: False},
2: {SYSFS_SLOT_KEY: 1, MAPPED_SLOT_KEY: 2, SUPPORTS_IDENTIFY_KEY: False},
4: {SYSFS_SLOT_KEY: 3, MAPPED_SLOT_KEY: 3, SUPPORTS_IDENTIFY_KEY: False},
5: {SYSFS_SLOT_KEY: 4, MAPPED_SLOT_KEY: 4, SUPPORTS_IDENTIFY_KEY: False}
},
'3000000000000001': {
1: {SYSFS_SLOT_KEY: 0, MAPPED_SLOT_KEY: 5, SUPPORTS_IDENTIFY_KEY: False},
2: {SYSFS_SLOT_KEY: 1, MAPPED_SLOT_KEY: 6, SUPPORTS_IDENTIFY_KEY: False},
3: {SYSFS_SLOT_KEY: 2, MAPPED_SLOT_KEY: 7, SUPPORTS_IDENTIFY_KEY: False}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def detect(self):
# down this path.
return HARDWARE, NODE

for enc in get_ses_enclosures(product, False):
for enc in get_ses_enclosures(False):
if enc.is_mseries:
HARDWARE = 'ECHOWARP'
if enc.product == '4024Sp':
Expand Down

0 comments on commit 9993ac1

Please sign in to comment.