17
17
LOG = logging .getLogger (__name__ )
18
18
CONF = cfg .CONF
19
19
20
- # Register config options for netapp_nvme backend
21
- CONF .register_opts (options .netapp_proxy_opts , group = "netapp_nvme" )
22
- CONF .register_opts (options .netapp_connection_opts , group = "netapp_nvme" )
23
- CONF .register_opts (options .netapp_transport_opts , group = "netapp_nvme" )
24
- CONF .register_opts (options .netapp_basicauth_opts , group = "netapp_nvme" )
25
- CONF .register_opts (options .netapp_provisioning_opts , group = "netapp_nvme" )
26
- CONF .register_opts (options .netapp_cluster_opts , group = "netapp_nvme" )
27
- CONF .register_opts (options .netapp_san_opts , group = "netapp_nvme" )
28
- CONF .register_opts (volume_driver .volume_opts , group = "netapp_nvme" )
20
+ # Configuration options for dynamic NetApp driver
21
+ # Using cinder.volume.configuration approach for better abstraction
22
+ NETAPP_DYNAMIC_OPTS = [
23
+ options .netapp_proxy_opts ,
24
+ options .netapp_connection_opts ,
25
+ options .netapp_transport_opts ,
26
+ options .netapp_basicauth_opts ,
27
+ options .netapp_provisioning_opts ,
28
+ options .netapp_cluster_opts ,
29
+ options .netapp_san_opts ,
30
+ volume_driver .volume_opts ,
31
+ ]
29
32
30
33
31
34
class NetappCinderDynamicDriver (NetAppNVMeStorageLibrary ):
@@ -48,11 +51,64 @@ def __init__(self, *args, **kwargs):
48
51
driver_protocol = kwargs .pop ("driver_protocol" , "nvme" )
49
52
self .app_version = kwargs .get ("app_version" , "1.0.0" )
50
53
54
+ self ._setup_configuration (** kwargs )
55
+
51
56
super ().__init__ (driver_name , driver_protocol , ** kwargs )
52
57
self .ssc_library = None
53
58
self .perf_library = None
54
59
self .init_capabilities ()
55
60
61
+ def _setup_configuration (self , ** kwargs ):
62
+ """Setup configuration using cinder.volume.configuration module."""
63
+ from cinder .volume import configuration
64
+
65
+ config_obj = kwargs .get ("configuration" , None )
66
+
67
+ if config_obj :
68
+ # here we can access any cinder-provided config .
69
+ self .configuration = config_obj
70
+ config_group = getattr (config_obj , "config_group" , "netapp_nvme" )
71
+
72
+ # Register NetApp-specific options using configuration.append()
73
+ # Following the exact pattern from upstream NetApp drivers
74
+
75
+ try :
76
+ for opt_group in NETAPP_DYNAMIC_OPTS :
77
+ self .configuration .append_config_values (opt_group )
78
+
79
+ LOG .info (
80
+ "Registered NetApp configuration options for group: %s" ,
81
+ config_group ,
82
+ )
83
+
84
+ except Exception as e :
85
+ LOG .warning ("Failed to register configuration options: %s" , e )
86
+ # Continue default configuration handling for backward compatibility
87
+ else :
88
+ # Testing/Fallback: Create configuration object with all options
89
+ config_group = "netapp_nvme"
90
+ self .configuration = configuration .Configuration (
91
+ volume_driver .volume_opts , config_group = config_group
92
+ )
93
+
94
+ # Register additional NetApp options for testing
95
+ try :
96
+ for opt_group in NETAPP_DYNAMIC_OPTS :
97
+ if (
98
+ opt_group != volume_driver .volume_opts
99
+ ): # Avoid duplicate registration
100
+ self .configuration .append_config_values (opt_group )
101
+
102
+ LOG .info (
103
+ "Registered NetApp configuration options for testing group: %s" ,
104
+ config_group ,
105
+ )
106
+
107
+ except Exception as e :
108
+ LOG .warning (
109
+ "Failed to register configuration options for testing: %s" , e
110
+ )
111
+
56
112
@property
57
113
def supported (self ):
58
114
# Used by Cinder to determine whether this driver is active/enabled
@@ -269,14 +325,14 @@ def _get_svm_specific_pools(self, svm_name, client):
269
325
270
326
# Get real capacity from NetApp - use fallback values if API fails
271
327
try :
272
- cap = client . get_flexvol_capacity ( vol_name )
328
+ cap = self . _get_flexvol_capacity_with_fallback ( client , vol_name )
273
329
if isinstance (cap , dict ):
274
330
if "size-total" in cap and "size-available" in cap :
275
- total_bytes = int ( cap ["size-total" ])
276
- free_bytes = int ( cap ["size-available" ])
331
+ total_bytes = cap ["size-total" ]
332
+ free_bytes = cap ["size-available" ]
277
333
elif "size_total" in cap and "size_available" in cap :
278
- total_bytes = int ( cap ["size_total" ])
279
- free_bytes = int ( cap ["size_available" ])
334
+ total_bytes = cap ["size_total" ]
335
+ free_bytes = cap ["size_available" ]
280
336
else :
281
337
LOG .warning (
282
338
"Unexpected capacity format for %s: %s" ,
@@ -513,6 +569,131 @@ def set_throttle(self):
513
569
# Got AttributeError
514
570
pass
515
571
572
+ def _get_flexvol_capacity_with_fallback (self , client , vol_name ):
573
+ """Get FlexVol capacity with custom volume name to junction path mapping."""
574
+ # TODO : find a API endpoint to fetch the junction path with svm and pool
575
+ try :
576
+ # First try the standard method
577
+ return client .get_flexvol_capacity (vol_name )
578
+ except Exception as e :
579
+ LOG .debug ("Standard capacity retrieval failed for %s: %s" , vol_name , e )
580
+
581
+ try :
582
+ # Use the same query pattern as list_flexvols() but with capacity fields
583
+ query = {
584
+ "type" : "rw" ,
585
+ "style" : "flex*" , # Match both 'flexvol' and 'flexgroup'
586
+ "is_svm_root" : "false" ,
587
+ "error_state.is_inconsistent" : "false" ,
588
+ "state" : "online" ,
589
+ "fields" : "name,space.available,space.size,nas.path" ,
590
+ }
591
+
592
+ # Get all volumes like list_flexvols() does
593
+ volumes_response = client .send_request (
594
+ "/storage/volumes/" , "get" , query = query
595
+ )
596
+ records = volumes_response .get ("records" , [])
597
+
598
+ # Filter for the specific volume we want with multiple matching patterns
599
+ target_volume = None
600
+ for volume in records :
601
+ volume_name = volume .get ("name" , "" )
602
+ volume_path = volume .get ("nas" , {}).get ("path" , "" )
603
+
604
+ # Pattern 1: Exact name match [Ideal scenario]
605
+ # but keeping other patterns just to be safe,
606
+ # we can delete it in future
607
+ # I might have to circle back here ,
608
+ # once I find a API endpoint to fetch
609
+ # the junction path with svm name and pool name
610
+ if volume_name == vol_name :
611
+ target_volume = volume
612
+ LOG .debug ("Found target volume by exact name: %s" , vol_name )
613
+ break
614
+
615
+ # Pattern 2: Path normalization (handle leading slash differences)
616
+ if volume_path :
617
+ normalized_vol_path = volume_path .lstrip ("/" )
618
+ normalized_target_path = vol_name .lstrip ("/" )
619
+ if normalized_vol_path == normalized_target_path :
620
+ target_volume = volume
621
+ LOG .debug (
622
+ "Found target volume by path normalization: %s -> %s" ,
623
+ vol_name ,
624
+ volume_path ,
625
+ )
626
+ break
627
+
628
+ # Pattern 3: sto_lun1 -> lun1 matches /lun1
629
+ if vol_name and volume_path and "_" in vol_name :
630
+ name_suffix = vol_name .split ("_" )[- 1 ]
631
+ normalized_vol_path = volume_path .lstrip ("/" )
632
+ if normalized_vol_path == name_suffix :
633
+ target_volume = volume
634
+ LOG .debug (
635
+ "Found target volume by name suffix mapping: %s -> %s" ,
636
+ vol_name ,
637
+ volume_path ,
638
+ )
639
+ break
640
+
641
+ # Pattern 4: sto_lun1 -> sto-lun1 matches /sto-lun1
642
+ if vol_name and volume_path :
643
+ hyphenated_name = vol_name .replace ("_" , "-" )
644
+ normalized_vol_path = volume_path .lstrip ("/" )
645
+ if normalized_vol_path == hyphenated_name :
646
+ target_volume = volume
647
+ LOG .debug (
648
+ "Found target volume by hyphen name mapping: %s -> %s" ,
649
+ vol_name ,
650
+ volume_path ,
651
+ )
652
+ break
653
+
654
+ # Pattern 5: handle cases where path uses hyphens
655
+ # but name uses underscores
656
+ if vol_name and volume_path :
657
+ normalized_vol_path = volume_path .lstrip ("/" )
658
+ if normalized_vol_path .replace ("-" , "_" ) == vol_name :
659
+ target_volume = volume
660
+ LOG .debug (
661
+ "Found target volume by separator conversion: %s -> %s" ,
662
+ vol_name ,
663
+ volume_path ,
664
+ )
665
+ break
666
+
667
+ if not target_volume :
668
+ volume_names = [vol .get ("name" , "unknown" ) for vol in records ]
669
+ volume_paths = [
670
+ vol .get ("nas" , {}).get ("path" , "no-path" ) for vol in records
671
+ ]
672
+ raise Exception (
673
+ f"Could not find volume { vol_name } . "
674
+ f"Available volumes: { volume_names } . "
675
+ f"Available paths: { volume_paths } ."
676
+ )
677
+
678
+ # Extract capacity information
679
+ space_info = target_volume .get ("space" , {})
680
+ total_size = space_info .get ("size" , 0 )
681
+ available_size = space_info .get ("available" , 0 )
682
+
683
+ return {
684
+ "size-total" : float (total_size ),
685
+ "size-available" : float (available_size ),
686
+ }
687
+
688
+ except Exception as custom_e :
689
+ LOG .warning (
690
+ "Custom capacity retrieval also failed for %s: %s" ,
691
+ vol_name ,
692
+ custom_e ,
693
+ )
694
+ # Return None to trigger fallback values in the calling code
695
+ raise custom_e
696
+
516
697
def _init_rest_client (self , hostname , username , password , vserver ):
517
698
"""Create a NetApp REST client for the specified SVM.
518
699
0 commit comments