Skip to content

Commit 90d5e36

Browse files
authored
Merge pull request #1074 from rackerlabs/PUC-1010
feat: Netapp Cinder driver for loading cinder cleanly with minimal cinder conf
2 parents afce6fb + 5683572 commit 90d5e36

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
"""Metadata-based backend config."""
2+
3+
from cinder import exception
4+
from cinder.volume import driver as volume_driver
5+
from cinder.volume.drivers.netapp import options
6+
from cinder.volume.drivers.netapp.dataontap.block_cmode import (
7+
NetAppBlockStorageCmodeLibrary,
8+
)
9+
from cinder.volume.drivers.netapp.dataontap.client.client_cmode_rest import (
10+
RestClient as RestNaServer,
11+
)
12+
from oslo_config import cfg
13+
from oslo_log import log as logging
14+
15+
# Dev: from remote_pdb import RemotePdb
16+
17+
LOG = logging.getLogger(__name__)
18+
CONF = cfg.CONF
19+
20+
# Register necessary config options under a unique group name 'dynamic_netapp'
21+
CONF.register_opts(options.netapp_connection_opts, group="dynamic_netapp")
22+
CONF.register_opts(options.netapp_transport_opts, group="dynamic_netapp")
23+
CONF.register_opts(options.netapp_basicauth_opts, group="dynamic_netapp")
24+
CONF.register_opts(options.netapp_provisioning_opts, group="dynamic_netapp")
25+
CONF.register_opts(options.netapp_cluster_opts, group="dynamic_netapp")
26+
CONF.register_opts(options.netapp_san_opts, group="dynamic_netapp")
27+
CONF.register_opts(volume_driver.volume_opts, group="dynamic_netapp")
28+
29+
# CONF.set_override("storage_protocol", "NVMe", group="dynamic_netapp")
30+
# CONF.set_override("netapp_storage_protocol", "NVMe", group="dynamic_netapp")
31+
# Upstream NetApp driver registers this option with choices=["iSCSI", "FC"]
32+
# So "NVMe" will raise a ValueError at boot. Instead, we handle this per-volume below.
33+
34+
35+
class NetappCinderDynamicDriver(NetAppBlockStorageCmodeLibrary):
36+
"""Metadata-based backend config."""
37+
38+
def __init__(self, *args, **kwargs):
39+
# NetApp driver requires 'driver_name' and 'driver_protocol'
40+
# These are mandatory for the superclass constructor
41+
driver_name = kwargs.pop("driver_name", "NetappDynamicCmode") # noqa: F841
42+
driver_protocol = kwargs.pop("driver_protocol", "NVMe") # noqa: F841
43+
super().__init__(
44+
*args,
45+
driver_name="NetApp_Dynamic",
46+
driver_protocol="dynamic",
47+
**kwargs,
48+
)
49+
self.init_capabilities() # Needed by scheduler via get_volume_stats()
50+
self.initialized = False # Required by set_initialized()
51+
52+
@property
53+
def supported(self):
54+
# Used by Cinder to determine whether this driver is active/enabled
55+
return True
56+
57+
def get_version(self):
58+
# Called at Cinder service startup to report backend driver version
59+
return "NetappCinderDynamicDriver 1.0"
60+
61+
def init_capabilities(self):
62+
# Required by Cinder schedulers — called from get_volume_stats()
63+
# If removed, scheduling filters based on capabilities may fail
64+
max_over_subscription_ratio = (self.configuration.max_over_subscription_ratio,)
65+
self._capabilities = {
66+
"thin_provisioning_support": True,
67+
"thick_provisioning_support": True,
68+
"multiattach": True,
69+
"snapshot_support": True,
70+
"max_over_subscription_ratio": max_over_subscription_ratio,
71+
}
72+
73+
def set_initialized(self):
74+
# Called by Cinder VolumeManager at the end of init_host()
75+
# If not defined, VolumeManager may assume the driver is not ready
76+
self.initialized = True
77+
78+
# NetAppBlockStorageCmodeLibrary expects self.ssc_library to be initialized
79+
# during setup.
80+
# In the normal NetApp driver, this is done in do_setup().
81+
# Cinder expects drivers to return a dict with a specific
82+
# schema from get_volume_stats().
83+
# This expected schema is:
84+
# Defined in cinder.volume.driver.BaseVD.get_volume_stats()the base driver class
85+
# And used later by scheduler and service capability reporting
86+
# cinder/volume/driver.py
87+
# get_volume_stats() inside BaseVD
88+
# _update_volume_stats - contains the keys
89+
# _update_pools_and_stats
90+
91+
def get_volume_stats(self, refresh=False):
92+
# Called from VolumeManager._report_driver_status()
93+
# Scheduler and Service report use this to advertise backend capabilities
94+
# "storage_protocol": "NVMe"Used only for reporting, not actual volume logic
95+
return {
96+
"volume_backend_name": "DynamicSVM",
97+
"vendor_name": "NetApp",
98+
"driver_version": "1.0",
99+
"storage_protocol": "NVMe",
100+
"pools": [self._get_dynamic_pool_stats()],
101+
}
102+
103+
def _get_dynamic_pool_stats(self):
104+
# Used internally by get_volume_stats(). The keys listed here are standard
105+
# and expected by Cinder's scheduler filters.
106+
# Reference: https://docs.openstack.org/cinder/latest/contributor/drivers.html#reporting-pool-information
107+
return {
108+
"pool_name": "dynamic_pool",
109+
"total_capacity_gb": 1000,
110+
"free_capacity_gb": 800,
111+
"reserved_percentage": 0,
112+
"max_over_subscription_ratio": 20.0,
113+
"provisioned_capacity_gb": 200,
114+
"allocated_capacity_gb": 100,
115+
"thin_provisioning_support": True,
116+
"thick_provisioning_support": False,
117+
"multiattach": True,
118+
"QoS_support": False,
119+
"compression_support": False,
120+
}
121+
122+
def get_filter_function(self):
123+
# Required for Cinder's scheduler. If not present, Cinder logs an AttributeError
124+
return self.configuration.safe_get("filter_function") or None
125+
126+
def get_goodness_function(self):
127+
# Paired with get_filter_function for scoring
128+
return self.configuration.safe_get("goodness_function") or None
129+
130+
def do_setup(self, context):
131+
# Required by VolumeDriver base class.
132+
# In our case, all backend config is injected per volume,
133+
# so we do not need static setup.
134+
self.ssc_library = "" # Set to avoid crash in _get_pool_stats()
135+
136+
def check_for_setup_error(self):
137+
# Called after do_setup() — used to validate static config.
138+
# In our case, there's no static setup, so it's a no-op.
139+
LOG.info(
140+
"NetApp Dynamic Driver: No setup error check. Validating at volume runtime."
141+
)
142+
143+
def update_provider_info(self, *args, **kwargs):
144+
# Called during _sync_provider_info() in VolumeManager.
145+
# If not implemented, Cinder raises a TypeError during service startup.
146+
# Wrote this logic because it was registered with 3 and was called using 2 args
147+
# There is issue with in-built drivers calling logic
148+
if len(args) == 2:
149+
volumes, snapshots = args
150+
elif len(args) >= 3:
151+
_, volumes, snapshots = args[:3]
152+
else:
153+
raise TypeError(
154+
"update_provider_info() expects at least volumes and snapshots."
155+
)
156+
return {}, {}
157+
158+
def set_throttle(self):
159+
# Got AttributeError
160+
pass
161+
162+
# Required if inheriting from block_cmode.
163+
# Default uses ZAPI to delete old QoS groups.
164+
# Since we're using REST and dynamic config, we override this to avoid ZAPI use.
165+
166+
def _mark_qos_policy_group_for_deletion(self, *args, **kwargs):
167+
LOG.debug("Skipping ZAPI-based QoS deletion in dynamic REST driver.")
168+
169+
def _init_rest_client(self, hostname, username, password, vserver):
170+
# Called from create_volume() to create per-SVM REST connection
171+
# This avoids use of global CONF and uses metadata-driven parameters
172+
return RestNaServer(
173+
hostname=hostname,
174+
username=username,
175+
password=password,
176+
vserver=vserver,
177+
api_trace_pattern="(.*)",
178+
private_key_file=None,
179+
certificate_file=None,
180+
ca_certificate_file=None,
181+
certificate_host_validation=False,
182+
transport_type="https",
183+
ssl_cert_path=None,
184+
ssl_cert_password=None,
185+
port=443,
186+
)
187+
188+
def clean_volume_file_locks(self, volume):
189+
# Got this when volume was created and mocked the NetApp connection.
190+
# When creation failed,
191+
# it started its cleanup process and errored out for this method.
192+
# In our case, REST-based NetApp doesn’t need this,
193+
# but must be present to avoid errors.
194+
LOG.debug("No-op clean_volume_file_locks in dynamic driver")
195+
196+
def create_volume(self, volume):
197+
# Called directly by Cinder during volume create workflow (create_volume.py)
198+
# This is where we extract runtime metadata (hostname, creds, protocol, etc.)
199+
# from volume type extra_specs and establish REST client connection.
200+
specs = volume.volume_type.extra_specs
201+
hostname = specs.get("netapp:svm_hostname")
202+
username = specs.get("netapp:svm_username")
203+
password = specs.get("netapp:svm_password")
204+
vserver = specs.get("netapp:svm_vserver")
205+
protocol = specs.get("netapp:svm_protocol", "NVMe")
206+
207+
if not all([hostname, username, password, vserver]):
208+
raise exception.VolumeBackendAPIException(data="Missing NetApp metadata")
209+
210+
client = self._init_rest_client(hostname, username, password, vserver) # noqa: F841
211+
212+
if protocol == "iscsi":
213+
LOG.info("Provisioning via iSCSI")
214+
elif protocol == "NVMe":
215+
LOG.info("Provisioning via NVMe")
216+
# TODO: Inherit these from client_cmode
217+
# Call create or get NVMe subsystem
218+
# Add host initiator to subsystem
219+
# Create namespace backed by FlexVol
220+
# Map namespace to subsystem
221+
else:
222+
LOG.info(" .WIP. ")

0 commit comments

Comments
 (0)