@@ -110,7 +110,18 @@ def get_current_ns_ids(controller_name):
110110 output = process .run (cmd , shell = True , sudo = True , ignore_status = True ).stdout_text
111111 for line in output .splitlines ():
112112 if line .startswith ("[" ):
113- namespaces .append (int (line .split ()[1 ].split ("]" )[0 ]) + 1 )
113+ # Format is: [ 0]:0x1 where 0x1 is the namespace ID in hex
114+ if ':' in line :
115+ ns_id_hex = line .split (':' )[- 1 ].strip ()
116+ # Convert hex string (e.g., '0x1') to integer
117+ try :
118+ namespaces .append (int (ns_id_hex , 16 ))
119+ except ValueError :
120+ # If not hex, try decimal
121+ try :
122+ namespaces .append (int (ns_id_hex ))
123+ except ValueError :
124+ LOGGER .warning (f"Could not parse namespace ID from: { line } " )
114125 return namespaces
115126
116127
@@ -280,18 +291,142 @@ def attach_ns(ns_id, controller_name, cont_id):
280291 """
281292 attach the namespace_id to specified controller
282293
283- :param ns_id: namespace ID
294+ :param ns_id: namespace ID (string or int)
284295 :param controller_name: controller name
285296 :param cont_id: controller_ID
286297 """
298+ # Ensure ns_id is an integer for consistent comparison
299+ ns_id = int (ns_id )
287300 cmd = f"nvme attach-ns /dev/{ controller_name } --namespace-id={ ns_id } -controllers={ cont_id } "
288301 if not process .run (cmd , shell = True , ignore_status = True ):
289302 raise NvmeException ("namespaces attach command failed" )
290303 ns_rescan (controller_name )
304+ # Add delay to allow kernel to update namespace list after rescan
305+ time .sleep (2 )
291306 if not is_ns_exists (controller_name , ns_id ):
292307 raise NvmeException ("namespaces attached but not listing" )
293308
294309
310+ def get_supported_lba_formats (controller_name ):
311+ """
312+ Query and return supported LBA formats for the NVMe controller.
313+
314+ This function attempts to retrieve the LBA Format (LBAF) array from an
315+ existing namespace on the controller. The LBAF array is controller-wide,
316+ meaning all namespaces on the same controller share the same set of
317+ supported formats, though each namespace can select a different format.
318+
319+ :param controller_name: Name of the controller (e.g., 'nvme0')
320+ :return: List of dicts containing format details, each with keys:
321+ 'index', 'block_size', 'metadata_size', 'relative_performance', 'valid'
322+ :rtype: list
323+ :raises: NvmeException if unable to query formats
324+ """
325+ # Try to get formats from an existing namespace
326+ namespaces = get_current_ns_list (controller_name )
327+
328+ if namespaces :
329+ # Query first available namespace for LBAF array
330+ namespace = get_namespace_absolute_path (namespaces [0 ])
331+ cmd = f"nvme id-ns { namespace } -o json"
332+ try :
333+ result = process .run (cmd , shell = True , ignore_status = False , sudo = True )
334+ ns_data = json .loads (result .stdout_text )
335+
336+ # Extract and parse LBAF array
337+ lba_formats = []
338+ for idx , lbaf in enumerate (ns_data .get ('lbafs' , [])):
339+ # Check if format is valid (ds > 0 means valid data size)
340+ ds = lbaf .get ('ds' , 0 )
341+ if ds > 0 :
342+ lba_formats .append ({
343+ 'index' : idx ,
344+ 'block_size' : 2 ** ds , # Convert power-of-2 to actual size
345+ 'metadata_size' : lbaf .get ('ms' , 0 ),
346+ 'relative_performance' : lbaf .get ('rp' , 0 ),
347+ 'valid' : True
348+ })
349+
350+ if lba_formats :
351+ LOGGER .debug (f"Found { len (lba_formats )} valid LBA formats for { controller_name } " )
352+ return lba_formats
353+
354+ except (process .CmdError , json .JSONDecodeError , KeyError ) as e :
355+ LOGGER .warning (f"Failed to query LBA formats from namespace: { e } " )
356+
357+ # Fallback: If no namespace exists or query failed, return common formats
358+ # Most NVMe devices support at least 512B and 4KB formats at indices 0 and 1
359+ # This is a safe assumption based on NVMe specification common implementations
360+ LOGGER .warning (
361+ f"No namespace found on { controller_name } , using common format assumptions. "
362+ f"This fallback assumes FLBAS indices 0 and 1 are valid with 512B and 4KB blocks respectively."
363+ )
364+ return [
365+ {'index' : 0 , 'block_size' : 512 , 'metadata_size' : 0 ,
366+ 'relative_performance' : 0 , 'valid' : True },
367+ {'index' : 1 , 'block_size' : 4096 , 'metadata_size' : 0 ,
368+ 'relative_performance' : 0 , 'valid' : True }
369+ ]
370+
371+
372+ def get_optimal_flbas (controller_name ):
373+ """
374+ Determine the optimal FLBAS (Formatted LBA Size) index for namespace creation.
375+
376+ FLBAS is a namespace-specific field (bits 3:0) that selects which LBA format
377+ from the controller's LBAF array should be used. This function implements
378+ an intelligent selection strategy:
379+
380+ 1. Prefer FLBAS index 0 if it's valid (most common default)
381+ 2. If index 0 is invalid, select first format with metadata_size=0
382+ 3. Among formats with no metadata, prefer common block sizes (512B, 4KB)
383+
384+ :param controller_name: Name of the controller (e.g., 'nvme0')
385+ :return: FLBAS index (0-15) to use for namespace creation
386+ :rtype: int
387+ :raises: NvmeException if no valid format is found
388+ """
389+ try :
390+ formats = get_supported_lba_formats (controller_name )
391+
392+ if not formats :
393+ raise NvmeException (f"No valid LBA formats found for { controller_name } " )
394+
395+ # Strategy 1: Try index 0 first (most common default)
396+ for fmt in formats :
397+ if fmt ['index' ] == 0 and fmt ['valid' ]:
398+ LOGGER .info (f"Using FLBAS index 0 (block_size={ fmt ['block_size' ]} B) "
399+ f"for { controller_name } " )
400+ return 0
401+
402+ # Strategy 2: Find first format with no metadata
403+ formats_no_metadata = [f for f in formats if f ['metadata_size' ] == 0 ]
404+
405+ if formats_no_metadata :
406+ # Prefer common block sizes: 512B or 4KB
407+ for preferred_size in [512 , 4096 ]:
408+ for fmt in formats_no_metadata :
409+ if fmt ['block_size' ] == preferred_size :
410+ LOGGER .info (f"Using FLBAS index { fmt ['index' ]} "
411+ f"(block_size={ fmt ['block_size' ]} B) for { controller_name } " )
412+ return fmt ['index' ]
413+
414+ # If no preferred size found, use first format without metadata
415+ selected = formats_no_metadata [0 ]
416+ LOGGER .info (f"Using FLBAS index { selected ['index' ]} "
417+ f"(block_size={ selected ['block_size' ]} B) for { controller_name } " )
418+ return selected ['index' ]
419+
420+ # Strategy 3: Last resort - use first valid format even with metadata
421+ selected = formats [0 ]
422+ LOGGER .warning (f"All formats have metadata. Using FLBAS index { selected ['index' ]} "
423+ f"(block_size={ selected ['block_size' ]} B, metadata={ selected ['metadata_size' ]} B)" )
424+ return selected ['index' ]
425+
426+ except Exception as e :
427+ raise NvmeException (f"Failed to determine optimal FLBAS for { controller_name } : { e } " )
428+
429+
295430def create_full_capacity_ns (controller_name , shared_ns = False ):
296431 """
297432 Creates one namespace with full capacity
@@ -304,19 +439,112 @@ def create_full_capacity_ns(controller_name, shared_ns=False):
304439 create_one_ns ("1" , controller_name , ns_size , shared_ns = shared_ns )
305440
306441
307- def create_one_ns (ns_id , controller_name , ns_size , shared_ns = False ):
308- """
309- creates a single namespaces with given size and controller_id
310-
311- :param ns_id: Namespace ID
312- :param controller_name: name of the controller like nvme0/nvme1 etc..
313- :param ns_size: Size of the namespace that is going to be created
314- """
315- cmd = f"nvme create-ns /dev/{ controller_name } --nsze={ ns_size } --ncap={ ns_size } --flbas=0 --dps=0"
442+ def create_one_ns (ns_id , controller_name , ns_size , shared_ns = False , flbas = 0 ):
443+ """
444+ Creates a single namespace with given size and controller_id.
445+
446+ This function supports dynamic FLBAS (Formatted LBA Size) selection with
447+ intelligent fallback behavior:
448+
449+ **FLBAS Selection Modes:**
450+
451+ 1. **Default Mode (flbas=0)**: Backward compatible behavior
452+ - First attempts namespace creation with FLBAS=0
453+ - If FLBAS=0 fails, automatically detects optimal FLBAS and retries
454+ - Recommended for most use cases
455+
456+ 2. **Auto-Detect Mode (flbas=-1)**: Skip FLBAS=0 attempt
457+ - Immediately detects and uses optimal FLBAS value
458+ - Useful for devices known to have non-standard FLBAS configurations
459+ - Saves one failed attempt on such devices
460+
461+ 3. **Explicit Mode (flbas=1-15)**: Use specific FLBAS index
462+ - Uses the specified FLBAS value without auto-detection
463+ - No retry on failure
464+ - For advanced users who know their device's FLBAS requirements
465+
466+ :param ns_id: Namespace ID (typically 1-based)
467+ :param controller_name: Name of the controller like nvme0/nvme1 etc..
468+ :param ns_size: Size of the namespace in blocks (based on selected LBA format)
469+ :param shared_ns: Whether to create a shared namespace (default: False)
470+ :param flbas: FLBAS index to use:
471+ - 0 (default): Try FLBAS=0, auto-retry on failure
472+ - -1: Skip FLBAS=0, immediately use auto-detected optimal value
473+ - 1-15: Use explicit FLBAS index, no auto-detection
474+ :raises: NvmeException if namespace creation fails with all attempted FLBAS values
475+
476+ :example:
477+ # Standard usage (backward compatible)
478+ create_one_ns("1", "nvme0", 2097152)
479+
480+ # Force auto-detection for non-standard devices
481+ create_one_ns("1", "nvme0", 2097152, flbas=-1)
482+
483+ # Use explicit FLBAS value
484+ create_one_ns("1", "nvme0", 2097152, flbas=2)
485+ """
486+ # Determine retry strategy based on flbas parameter
487+ # Only enable auto-detection fallback for default flbas=0
488+ should_retry_with_auto_detect = (flbas == 0 )
489+
490+ # Handle explicit auto-detection request (flbas=-1)
491+ # This skips the FLBAS=0 attempt and goes straight to optimal detection
492+ if flbas == - 1 :
493+ try :
494+ flbas = get_optimal_flbas (controller_name )
495+ LOGGER .info (f"Auto-detected FLBAS={ flbas } for namespace creation on { controller_name } " )
496+ except NvmeException as e :
497+ LOGGER .error (f"Failed to auto-detect FLBAS: { e } " )
498+ raise NvmeException (f"Cannot create namespace: FLBAS auto-detection failed: { e } " )
499+
500+ # Build and execute namespace creation command
501+ cmd = f"nvme create-ns /dev/{ controller_name } --nsze={ ns_size } --ncap={ ns_size } --flbas={ flbas } --dps=0"
316502 if shared_ns :
317503 cmd = f"{ cmd } -m 1"
318- if process .system (cmd , shell = True , ignore_status = True ):
319- raise NvmeException (f"namespace create command failed { cmd } " )
504+
505+ result = process .system (cmd , shell = True , ignore_status = True )
506+
507+ # Intelligent retry: If creation failed with default FLBAS=0, try auto-detection
508+ # This provides automatic fallback for devices with non-standard FLBAS configurations
509+ if result != 0 and should_retry_with_auto_detect :
510+ LOGGER .warning (f"Namespace creation failed with FLBAS=0 on { controller_name } " )
511+ LOGGER .info ("Attempting auto-detection of optimal FLBAS value..." )
512+
513+ try :
514+ optimal_flbas = get_optimal_flbas (controller_name )
515+
516+ # Only retry if optimal FLBAS is different from what we tried
517+ if optimal_flbas != 0 :
518+ LOGGER .info (f"Retrying with auto-detected FLBAS={ optimal_flbas } " )
519+ cmd = f"nvme create-ns /dev/{ controller_name } --nsze={ ns_size } --ncap={ ns_size } --flbas={ optimal_flbas } --dps=0"
520+ if shared_ns :
521+ cmd = f"{ cmd } -m 1"
522+
523+ result = process .system (cmd , shell = True , ignore_status = True )
524+
525+ if result == 0 :
526+ LOGGER .info (f"Namespace creation succeeded with FLBAS={ optimal_flbas } " )
527+ flbas = optimal_flbas # Update for logging
528+ else :
529+ raise NvmeException (
530+ f"Namespace creation failed with both FLBAS=0 and auto-detected FLBAS={ optimal_flbas } . "
531+ f"Command: { cmd } "
532+ )
533+ else :
534+ raise NvmeException (
535+ f"Namespace creation failed with FLBAS=0 and auto-detection also suggests FLBAS=0. "
536+ f"Command: { cmd } "
537+ )
538+ except NvmeException as e :
539+ raise NvmeException (f"Namespace creation failed: { e } " )
540+ elif result != 0 :
541+ # Failed with user-specified FLBAS (not 0), don't retry
542+ raise NvmeException (
543+ f"Namespace create command failed with FLBAS={ flbas } . "
544+ f"Command: { cmd } . Exit code: { result } "
545+ )
546+
547+ # Attach namespace to controller(s)
320548 cont_id = get_controller_id (controller_name )
321549 if shared_ns :
322550 ctrls = get_alternate_controller_name (controller_name )
0 commit comments