blob: 9e7b7f44b02db62518705b7ea9042a9df933d1af [file] [log] [blame]
Biser Milanov7795e092023-05-30 13:24:09 +03001From 4c37d209a5968cfaf280bfb165691aff031b5e44 Mon Sep 17 00:00:00 2001
Biser Milanov109150a2023-05-26 12:05:31 +03002From: Biser Milanov <biser.milanov@storpool.com>
3Date: Tue, 23 May 2023 17:55:09 +0300
4Subject: [PATCH] storpool.py: Use StorPool's API for Attach/Detach
5
6Drop the use of the node-scoped file that helped manage volumes on that
7node.
8
9Volumes are now attached and detached by calling the StorPool API
10directly.
11
12Additionally refactor tests to reflect this.
13
14Change-Id: I0fabcde1dc249b9b09f99ee0b7d759432972314a
15---
16 os_brick/initiator/connectors/storpool.py | 48 ++++--
17 .../initiator/connectors/test_storpool.py | 151 +++++++++++-------
18 2 files changed, 132 insertions(+), 67 deletions(-)
19
20diff --git a/os_brick/initiator/connectors/storpool.py b/os_brick/initiator/connectors/storpool.py
Biser Milanov7795e092023-05-30 13:24:09 +030021index e1bd55c..ee4846e 100644
Biser Milanov109150a2023-05-26 12:05:31 +030022--- a/os_brick/initiator/connectors/storpool.py
23+++ b/os_brick/initiator/connectors/storpool.py
24@@ -76,16 +76,29 @@ class StorPoolConnector(base.BaseLinuxConnector):
25 if mode is None or mode not in ('rw', 'ro'):
26 raise exception.BrickException(
27 'Invalid access_mode specified in the connection data.')
28- req_id = 'brick-%s-%s' % (client_id, volume_id)
29- self._attach.add(req_id, {
30- 'volume': volume,
31- 'type': 'brick',
32- 'id': req_id,
33- 'rights': 1 if mode == 'ro' else 2,
34- 'volsnap': False
35- })
36- self._attach.sync(req_id, None)
37- return {'type': 'block', 'path': '/dev/storpool/' + volume}
38+ try:
39+ sp_ourid = self._attach.config()["SP_OURID"]
40+ except KeyError:
41+ raise exception.BrickException(
42+ 'SP_OURID missing, cannot connect volume %s' % volume_id)
43+
44+ try:
45+ self._attach.api().volumesReassignWait(
46+ {"reassign": [{"volume": volume, mode: [sp_ourid]}]})
47+ except Exception as exc:
48+ raise exception.BrickException(
49+ 'Communication with the StorPool API '
50+ 'failed: %s' % (exc)) from exc
51+
52+ try:
53+ volume_info = self._attach.api().volumeInfo(volume)
54+ except Exception as exc:
55+ raise exception.BrickException(
56+ 'Communication with the StorPool API '
57+ 'failed: %s' % (exc)) from exc
58+
Biser Milanov7795e092023-05-30 13:24:09 +030059+ sp_global_id = volume_info.globalId
Biser Milanov109150a2023-05-26 12:05:31 +030060+ return {'type': 'block', 'path': '/dev/storpool-byid/' + sp_global_id}
61
62 @utils.connect_volume_undo_prepare_result(unlink_after=True)
63 def disconnect_volume(self, connection_properties, device_info,
64@@ -124,9 +137,18 @@ class StorPoolConnector(base.BaseLinuxConnector):
65 raise exception.BrickException(
66 'Invalid StorPool connection data, no volume ID specified.')
67 volume = self._attach.volumeName(volume_id)
68- req_id = 'brick-%s-%s' % (client_id, volume_id)
69- self._attach.sync(req_id, volume)
70- self._attach.remove(req_id)
71+ try:
72+ sp_ourid = self._attach.config()["SP_OURID"]
73+ except KeyError:
74+ raise exception.BrickException(
75+ 'SP_OURID missing, cannot disconnect volume %s' % volume)
76+ try:
77+ self._attach.api().volumesReassignWait(
78+ {"reassign": [{"volume": volume, "detach": [sp_ourid]}]})
79+ except Exception as exc:
80+ raise exception.BrickException(
81+ 'Communication with the StorPool API '
82+ 'failed: %s' % (exc)) from exc
83
84 def get_search_path(self):
85 return '/dev/storpool'
86diff --git a/os_brick/tests/initiator/connectors/test_storpool.py b/os_brick/tests/initiator/connectors/test_storpool.py
87index 614deba..9cc1076 100644
88--- a/os_brick/tests/initiator/connectors/test_storpool.py
89+++ b/os_brick/tests/initiator/connectors/test_storpool.py
90@@ -27,44 +27,13 @@ def volumeNameExt(vid):
91
92 class MockStorPoolADB(object):
93 def __init__(self, log):
94- self.requests = {}
95- self.attached = {}
96+ pass
97
98 def api(self):
99 pass
100
101- def add(self, req_id, req):
102- if req_id in self.requests:
103- raise Exception('Duplicate MockStorPool request added')
104- self.requests[req_id] = req
105-
106- def remove(self, req_id):
107- req = self.requests.get(req_id, None)
108- if req is None:
109- raise Exception('Unknown MockStorPool request removed')
110- elif req['volume'] in self.attached:
111- raise Exception('Removing attached MockStorPool volume')
112- del self.requests[req_id]
113-
114- def sync(self, req_id, detached):
115- req = self.requests.get(req_id, None)
116- if req is None:
117- raise Exception('Unknown MockStorPool request synced')
118- volume = req.get('volume', None)
119- if volume is None:
120- raise Exception('MockStorPool request without volume')
121-
122- if detached is None:
123- if volume in self.attached:
124- raise Exception('Duplicate MockStorPool request synced')
125- self.attached[volume] = req
126- else:
127- if volume != detached:
128- raise Exception(
129- 'Mismatched volumes on a MockStorPool request removal')
130- elif detached not in self.attached:
131- raise Exception('MockStorPool request not attached yet')
132- del self.attached[detached]
133+ def config(self):
134+ return {"SP_OURID": 1}
135
136 def volumeName(self, vid):
137 return volumeNameExt(vid)
138@@ -101,6 +70,7 @@ class StorPoolConnectorTestCase(test_connector.ConnectorTestCase):
139 'client_id': 1,
140 'access_mode': 'rw',
141 }
142+ self.fakeGlobalId = 'OneNiceGlobalId'
143 self.fakeConnection = None
144 self.fakeSize = 1024 * 1024 * 1024
145
146@@ -109,30 +79,56 @@ class StorPoolConnectorTestCase(test_connector.ConnectorTestCase):
147 self.adb = self.connector._attach
148
149 def test_connect_volume(self):
150- self.assertNotIn(self.volumeName(self.fakeProp['volume']),
151- self.adb.attached)
152- conn = self.connector.connect_volume(self.fakeProp)
153- self.assertIn('type', conn)
154- self.assertIn('path', conn)
155- self.assertIn(self.volumeName(self.fakeProp['volume']),
156- self.adb.attached)
157-
158- self.assertEqual(self.connector.get_search_path(), '/dev/storpool')
159- paths = self.connector.get_volume_paths(self.fakeProp)
160- self.assertEqual(len(paths), 1)
161- self.assertEqual(paths[0],
162- "/dev/storpool/" +
163- self.volumeName(self.fakeProp['volume']))
164- self.fakeConnection = conn
165+ volume_name = volumeNameExt(self.fakeProp['volume'])
166+ api = mock.MagicMock(spec=['volumesReassignWait', 'volumeInfo'])
167+ api.volumesReassignWait = mock.MagicMock(spec=['__call__'])
168+ api.volumeInfo = mock.Mock(
169+ return_value={'data': {'globalId': self.fakeGlobalId}})
170+
171+ with mock.patch.object(
172+ self.adb, attribute='api', spec=['__call__']
173+ ) as fake_api:
174+ fake_api.return_value = api
175+
176+ conn = self.connector.connect_volume(self.fakeProp)
177+ self.assertIn('type', conn)
178+ self.assertIn('path', conn)
179+ self.assertEqual(conn['path'],
180+ '/dev/storpool-byid/' + self.fakeGlobalId)
181+ self.assertEqual(len(api.volumesReassignWait.mock_calls), 1)
182+ self.assertEqual(api.volumesReassignWait.mock_calls[0], mock.call(
183+ {'reassign': [{'volume': 'os--volume--sp-vol-1', 'rw': [1]}]}))
184+ self.assertEqual(len(api.volumeInfo.mock_calls), 1)
185+ self.assertEqual(api.volumeInfo.mock_calls[0],
186+ mock.call(volume_name))
187+
188+ self.assertEqual(self.connector.get_search_path(), '/dev/storpool')
189+
190+ paths = self.connector.get_volume_paths(self.fakeProp)
191+ self.assertEqual(len(paths), 1)
192+ self.assertEqual(paths[0],
193+ "/dev/storpool/" +
194+ self.volumeName(self.fakeProp['volume']))
195+ self.fakeConnection = conn
196
197 def test_disconnect_volume(self):
198 if self.fakeConnection is None:
199 self.test_connect_volume()
200- self.assertIn(self.volumeName(self.fakeProp['volume']),
201- self.adb.attached)
202- self.connector.disconnect_volume(self.fakeProp, None)
203- self.assertNotIn(self.volumeName(self.fakeProp['volume']),
204- self.adb.attached)
205+
206+ api = mock.MagicMock(spec=['volumesReassignWait'])
207+ api.volumesReassignWait = mock.MagicMock(spec=['__call__'])
208+
209+ with mock.patch.object(
210+ self.adb, attribute='api', spec=['__call__']
211+ ) as fake_api:
212+ fake_api.return_value = api
213+ reassign_wait_data = {'reassign': [
214+ {'volume': volumeNameExt(self.fakeProp['volume']),
215+ 'detach': [1]}]}
216+
217+ self.connector.disconnect_volume(self.fakeProp, None)
218+ self.assertEqual(api.volumesReassignWait.mock_calls[0],
219+ (mock.call(reassign_wait_data)))
220
221 def test_connect_exceptions(self):
222 """Raise exceptions on missing connection information"""
223@@ -146,6 +142,53 @@ class StorPoolConnectorTestCase(test_connector.ConnectorTestCase):
224 self.assertRaises(exception.BrickException,
225 self.connector.disconnect_volume, c, None)
226
227+ def test_sp_ourid_exceptions(self):
228+ """Raise exceptions on missing SP_OURID"""
229+ with mock.patch.object(self.connector._attach, 'config')\
230+ as fake_config:
231+ fake_config.return_value = {}
232+
233+ self.assertRaises(exception.BrickException,
234+ self.connector.connect_volume, self.fakeProp)
235+
236+ self.assertRaises(exception.BrickException,
237+ self.connector.disconnect_volume, self.fakeProp,
238+ None)
239+
240+ def test_sp_api_exceptions(self):
241+ """Handle SP API exceptions"""
242+ api = mock.MagicMock(spec=['volumesReassignWait', 'volumeInfo'])
243+ api.volumesReassignWait = mock.MagicMock(spec=['__call__'])
244+ api.volumesReassignWait.side_effect = Exception()
245+ api.volumeInfo = mock.MagicMock(spec=['__call__'])
246+
247+ with mock.patch.object(
248+ self.adb, attribute='api', spec=['__call__']
249+ ) as fake_api:
250+ fake_api.return_value = api
251+
252+ self.assertRaises(exception.BrickException,
253+ self.connector.connect_volume, self.fakeProp)
254+
255+ self.assertRaises(exception.BrickException,
256+ self.connector.disconnect_volume, self.fakeProp,
257+ None)
258+
259+ api.volumesReassignWait.side_effect = ""
260+ api.volumeInfo = Exception()
261+
262+ with mock.patch.object(
263+ self.adb, attribute='api', spec=['__call__']
264+ ) as fake_api:
265+ fake_api.return_value = api
266+
267+ self.assertRaises(exception.BrickException,
268+ self.connector.connect_volume, self.fakeProp)
269+
270+ self.assertRaises(exception.BrickException,
271+ self.connector.disconnect_volume, self.fakeProp,
272+ None)
273+
274 def test_extend_volume(self):
275 if self.fakeConnection is None:
276 self.test_connect_volume()
277--
2782.25.1
279