Skip to content

Commit e341cf2

Browse files
authored
Merge pull request #1129 from rackerlabs/PUC-1110
feat: Step 4.1: cinder.volume.configuration module to abstract conf registration
2 parents aaed2ea + 0bb783b commit e341cf2

File tree

1 file changed

+195
-14
lines changed

1 file changed

+195
-14
lines changed

python/cinder-understack/cinder_understack/dynamic_netapp_driver.py

Lines changed: 195 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@
1717
LOG = logging.getLogger(__name__)
1818
CONF = cfg.CONF
1919

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+
]
2932

3033

3134
class NetappCinderDynamicDriver(NetAppNVMeStorageLibrary):
@@ -48,11 +51,64 @@ def __init__(self, *args, **kwargs):
4851
driver_protocol = kwargs.pop("driver_protocol", "nvme")
4952
self.app_version = kwargs.get("app_version", "1.0.0")
5053

54+
self._setup_configuration(**kwargs)
55+
5156
super().__init__(driver_name, driver_protocol, **kwargs)
5257
self.ssc_library = None
5358
self.perf_library = None
5459
self.init_capabilities()
5560

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+
56112
@property
57113
def supported(self):
58114
# Used by Cinder to determine whether this driver is active/enabled
@@ -269,14 +325,14 @@ def _get_svm_specific_pools(self, svm_name, client):
269325

270326
# Get real capacity from NetApp - use fallback values if API fails
271327
try:
272-
cap = client.get_flexvol_capacity(vol_name)
328+
cap = self._get_flexvol_capacity_with_fallback(client, vol_name)
273329
if isinstance(cap, dict):
274330
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"]
277333
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"]
280336
else:
281337
LOG.warning(
282338
"Unexpected capacity format for %s: %s",
@@ -513,6 +569,131 @@ def set_throttle(self):
513569
# Got AttributeError
514570
pass
515571

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+
516697
def _init_rest_client(self, hostname, username, password, vserver):
517698
"""Create a NetApp REST client for the specified SVM.
518699

0 commit comments

Comments
 (0)