Skip to content

Commit 0f70489

Browse files
committed
feat(cinder-understack): split the classes like upstream
Follow the upstream pattern by having the actual driver class inherit from BaseVD and have a library class that's called instead of combining functionality.
1 parent 3bcfefd commit 0f70489

File tree

3 files changed

+148
-8
lines changed

3 files changed

+148
-8
lines changed

python/cinder-understack/cinder_understack/dynamic_netapp_driver.py

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from cinder import context
44
from cinder import exception
5+
from cinder import interface
56
from cinder.objects import volume_type as vol_type_obj
67
from cinder.volume import driver as volume_driver
78
from cinder.volume.drivers.netapp import options
@@ -31,8 +32,8 @@
3132
]
3233

3334

34-
class NetappCinderDynamicDriver(NetAppNVMeStorageLibrary):
35-
"""NetApp NVMe driver with dynamic SVM selection from volume types.
35+
class NetappDynamicLibrary(NetAppNVMeStorageLibrary):
36+
"""NetApp NVMe storage library with dynamic SVM selection from volume types.
3637
3738
Key difference from standard NetApp drivers:
3839
- Standard: One SVM per backend, all config in cinder.conf
@@ -964,6 +965,87 @@ def create_volume(self, volume):
964965
) from e
965966

966967

968+
@interface.volumedriver
969+
class NetappCinderDynamicDriver(volume_driver.BaseVD):
970+
"""NetApp NVMe driver with dynamic multi-SVM support.
971+
972+
This driver follows the standard Cinder pattern by inheriting from BaseVD
973+
and delegating storage operations to the NetappDynamicLibrary.
974+
"""
975+
976+
VERSION = "1.0.0"
977+
DRIVER_NAME = "NetApp_Dynamic_NVMe"
978+
979+
def __init__(self, *args, **kwargs):
980+
"""Initialize the driver and create library instance."""
981+
super().__init__(*args, **kwargs)
982+
self.library = NetappDynamicLibrary(self.DRIVER_NAME, "NVMe", **kwargs)
983+
984+
def do_setup(self, context):
985+
"""Setup the driver."""
986+
self.library.do_setup(context)
987+
988+
def check_for_setup_error(self):
989+
"""Check for setup errors."""
990+
self.library.check_for_setup_error()
991+
992+
def create_volume(self, volume):
993+
"""Create a volume."""
994+
return self.library.create_volume(volume)
995+
996+
def delete_volume(self, volume):
997+
"""Delete a volume."""
998+
return self.library.delete_volume(volume)
999+
1000+
def create_snapshot(self, snapshot):
1001+
"""Create a snapshot."""
1002+
return self.library.create_snapshot(snapshot)
1003+
1004+
def delete_snapshot(self, snapshot):
1005+
"""Delete a snapshot."""
1006+
return self.library.delete_snapshot(snapshot)
1007+
1008+
def create_volume_from_snapshot(self, volume, snapshot):
1009+
"""Create a volume from a snapshot."""
1010+
return self.library.create_volume_from_snapshot(volume, snapshot)
1011+
1012+
def create_cloned_volume(self, volume, src_vref):
1013+
"""Create a cloned volume."""
1014+
return self.library.create_cloned_volume(volume, src_vref)
1015+
1016+
def extend_volume(self, volume, new_size):
1017+
"""Extend a volume."""
1018+
return self.library.extend_volume(volume, new_size)
1019+
1020+
def initialize_connection(self, volume, connector):
1021+
"""Initialize connection to volume."""
1022+
return self.library.initialize_connection(volume, connector)
1023+
1024+
def terminate_connection(self, volume, connector, **kwargs):
1025+
"""Terminate connection to volume."""
1026+
return self.library.terminate_connection(volume, connector, **kwargs)
1027+
1028+
def get_volume_stats(self, refresh=False):
1029+
"""Get volume stats."""
1030+
return self.library.get_volume_stats(refresh)
1031+
1032+
def update_provider_info(self, volumes, snapshots):
1033+
"""Update provider info."""
1034+
return self.library.update_provider_info(volumes, snapshots)
1035+
1036+
def create_export(self, context, volume, connector):
1037+
"""Create export for volume."""
1038+
return self.library.create_export(context, volume, connector)
1039+
1040+
def ensure_export(self, context, volume):
1041+
"""Ensure export for volume."""
1042+
return self.library.ensure_export(context, volume)
1043+
1044+
def remove_export(self, context, volume):
1045+
"""Remove export for volume."""
1046+
return self.library.remove_export(context, volume)
1047+
1048+
9671049
# NOTES
9681050
# Namespace: Manually created because we skip standard do_setup()
9691051
# Pool: Custom svm#flexvol format to support multi-SVM
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Test NetApp dynamic driver implementation."""
2+
3+
from unittest import mock
4+
5+
from cinder.tests.unit import test
6+
from cinder.tests.unit.volume.drivers.netapp import fakes as na_fakes
7+
8+
from cinder_understack import dynamic_netapp_driver
9+
10+
11+
class NetappDynamicDriverTestCase(test.TestCase):
12+
"""Test case for NetappCinderDynamicDriver."""
13+
14+
def setUp(self):
15+
"""Set up test case."""
16+
super().setUp()
17+
18+
kwargs = {
19+
"configuration": self.get_config_base(),
20+
"host": "openstack@netapp_dynamic",
21+
}
22+
self.driver = dynamic_netapp_driver.NetappCinderDynamicDriver(**kwargs)
23+
self.library = self.driver.library
24+
25+
def get_config_base(self):
26+
"""Get base configuration for testing."""
27+
return na_fakes.create_configuration()
28+
29+
def test_driver_has_correct_attributes(self):
30+
"""Test that driver has expected attributes."""
31+
self.assertEqual("1.0.0", self.driver.VERSION)
32+
self.assertEqual("NetApp_Dynamic_NVMe", self.driver.DRIVER_NAME)
33+
34+
def test_driver_has_library_instance(self):
35+
"""Test that driver has library instance."""
36+
self.assertIsInstance(self.library, dynamic_netapp_driver.NetappDynamicLibrary)
37+
38+
def test_library_inherits_from_netapp_library(self):
39+
"""Test that library inherits from NetApp NVMe library."""
40+
from cinder.volume.drivers.netapp.dataontap.nvme_library import (
41+
NetAppNVMeStorageLibrary,
42+
)
43+
44+
self.assertIsInstance(self.library, NetAppNVMeStorageLibrary)
45+
46+
@mock.patch.object(dynamic_netapp_driver.NetappDynamicLibrary, "do_setup")
47+
def test_do_setup_calls_library(self, mock_do_setup):
48+
"""Test that do_setup delegates to library."""
49+
context = mock.Mock()
50+
self.driver.do_setup(context)
51+
mock_do_setup.assert_called_once_with(context)
52+
53+
@mock.patch.object(dynamic_netapp_driver.NetappDynamicLibrary, "create_volume")
54+
def test_create_volume_calls_library(self, mock_create_volume):
55+
"""Test that create_volume delegates to library."""
56+
volume = mock.Mock()
57+
self.driver.create_volume(volume)
58+
mock_create_volume.assert_called_once_with(volume)
59+
60+
@mock.patch.object(dynamic_netapp_driver.NetappDynamicLibrary, "get_volume_stats")
61+
def test_get_volume_stats_calls_library(self, mock_get_volume_stats):
62+
"""Test that get_volume_stats delegates to library."""
63+
self.driver.get_volume_stats(refresh=True)
64+
mock_get_volume_stats.assert_called_once_with(True)

python/cinder-understack/cinder_understack/tests/test_noop.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)