]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/tests/test_iscsi.py
import ceph 16.2.6
[ceph.git] / ceph / src / pybind / mgr / dashboard / tests / test_iscsi.py
CommitLineData
cd265ab1 1# pylint: disable=too-many-public-methods, too-many-lines
11fdf7f2
TL
2
3import copy
4import errno
5import json
92f5a8d4 6import unittest
11fdf7f2 7
92f5a8d4
TL
8try:
9 import mock
10except ImportError:
11 import unittest.mock as mock
12
cd265ab1
TL
13from mgr_module import ERROR_MSG_NO_INPUT_FILE
14
11fdf7f2
TL
15from .. import mgr
16from ..controllers.iscsi import Iscsi, IscsiTarget
f67539c2 17from ..rest_client import RequestException
11fdf7f2
TL
18from ..services.iscsi_client import IscsiClient
19from ..services.orchestrator import OrchClient
f67539c2
TL
20from ..tools import NotificationQueue, TaskManager
21from . import CLICommandTestMixin # pylint: disable=no-name-in-module
22from . import CmdException # pylint: disable=no-name-in-module
23from . import ControllerTestCase # pylint: disable=no-name-in-module
24from . import KVStoreMockMixin # pylint: disable=no-name-in-module
11fdf7f2
TL
25
26
92f5a8d4 27class IscsiTestCli(unittest.TestCase, CLICommandTestMixin):
11fdf7f2
TL
28
29 def setUp(self):
30 self.mock_kv_store()
31 # pylint: disable=protected-access
32 IscsiClientMock._instance = IscsiClientMock()
33 IscsiClient.instance = IscsiClientMock.instance
34
35 def test_cli_add_gateway_invalid_url(self):
36 with self.assertRaises(CmdException) as ctx:
37 self.exec_cmd('iscsi-gateway-add', name='node1',
cd265ab1 38 inbuf='http:/hello.com')
11fdf7f2
TL
39
40 self.assertEqual(ctx.exception.retcode, -errno.EINVAL)
41 self.assertEqual(str(ctx.exception),
42 "Invalid service URL 'http:/hello.com'. Valid format: "
43 "'<scheme>://<username>:<password>@<host>[:port]'.")
44
cd265ab1
TL
45 def test_cli_add_gateway_empty_url(self):
46 with self.assertRaises(CmdException) as ctx:
47 self.exec_cmd('iscsi-gateway-add', name='node1',
48 inbuf='')
49
50 self.assertEqual(ctx.exception.retcode, -errno.EINVAL)
522d829b 51 self.assertIn(ERROR_MSG_NO_INPUT_FILE, str(ctx.exception))
cd265ab1 52
11fdf7f2
TL
53 def test_cli_add_gateway(self):
54 self.exec_cmd('iscsi-gateway-add', name='node1',
cd265ab1 55 inbuf='https://admin:admin@10.17.5.1:5001')
11fdf7f2 56 self.exec_cmd('iscsi-gateway-add', name='node2',
cd265ab1 57 inbuf='https://admin:admin@10.17.5.2:5001')
11fdf7f2
TL
58 iscsi_config = json.loads(self.get_key("_iscsi_config"))
59 self.assertEqual(iscsi_config['gateways'], {
60 'node1': {
61 'service_url': 'https://admin:admin@10.17.5.1:5001'
62 },
63 'node2': {
64 'service_url': 'https://admin:admin@10.17.5.2:5001'
65 }
66 })
67
68 def test_cli_remove_gateway(self):
69 self.test_cli_add_gateway()
70 self.exec_cmd('iscsi-gateway-rm', name='node1')
71 iscsi_config = json.loads(self.get_key("_iscsi_config"))
72 self.assertEqual(iscsi_config['gateways'], {
73 'node2': {
74 'service_url': 'https://admin:admin@10.17.5.2:5001'
75 }
76 })
77
92f5a8d4
TL
78
79class IscsiTestController(ControllerTestCase, KVStoreMockMixin):
80
81 @classmethod
82 def setup_server(cls):
f67539c2
TL
83 NotificationQueue.start_queue()
84 TaskManager.init()
9f95a23c 85 OrchClient.instance().available = lambda: False
92f5a8d4
TL
86 mgr.rados.side_effect = None
87 # pylint: disable=protected-access
88 Iscsi._cp_config['tools.authenticate.on'] = False
89 IscsiTarget._cp_config['tools.authenticate.on'] = False
90 cls.setup_controllers([Iscsi, IscsiTarget])
91
f67539c2
TL
92 @classmethod
93 def tearDownClass(cls):
94 NotificationQueue.stop()
95
92f5a8d4
TL
96 def setUp(self):
97 self.mock_kv_store()
98 self.CONFIG_KEY_DICT['_iscsi_config'] = '''
99 {
100 "gateways": {
101 "node1": {
102 "service_url": "https://admin:admin@10.17.5.1:5001"
103 },
104 "node2": {
105 "service_url": "https://admin:admin@10.17.5.2:5001"
106 }
107 }
108 }
109 '''
110 # pylint: disable=protected-access
111 IscsiClientMock._instance = IscsiClientMock()
112 IscsiClient.instance = IscsiClientMock.instance
113
11fdf7f2
TL
114 def test_enable_discoveryauth(self):
115 discoveryauth = {
116 'user': 'myiscsiusername',
117 'password': 'myiscsipassword',
118 'mutual_user': 'myiscsiusername2',
119 'mutual_password': 'myiscsipassword2'
120 }
121 self._put('/api/iscsi/discoveryauth', discoveryauth)
122 self.assertStatus(200)
123 self.assertJsonBody(discoveryauth)
124 self._get('/api/iscsi/discoveryauth')
125 self.assertStatus(200)
126 self.assertJsonBody(discoveryauth)
127
1911f103
TL
128 def test_bad_discoveryauth(self):
129 discoveryauth = {
130 'user': 'myiscsiusername',
131 'password': 'myiscsipasswordmyiscsipasswordmyiscsipassword',
132 'mutual_user': '',
133 'mutual_password': ''
134 }
135 put_response = {
136 'detail': 'Bad authentication',
137 'code': 'target_bad_auth',
138 'component': 'iscsi'
139 }
140 get_response = {
141 'user': '',
142 'password': '',
143 'mutual_user': '',
144 'mutual_password': ''
145 }
146 self._put('/api/iscsi/discoveryauth', discoveryauth)
147 self.assertStatus(400)
148 self.assertJsonBody(put_response)
149 self._get('/api/iscsi/discoveryauth')
150 self.assertStatus(200)
151 self.assertJsonBody(get_response)
152
11fdf7f2
TL
153 def test_disable_discoveryauth(self):
154 discoveryauth = {
155 'user': '',
156 'password': '',
157 'mutual_user': '',
158 'mutual_password': ''
159 }
160 self._put('/api/iscsi/discoveryauth', discoveryauth)
161 self.assertStatus(200)
162 self.assertJsonBody(discoveryauth)
163 self._get('/api/iscsi/discoveryauth')
164 self.assertStatus(200)
165 self.assertJsonBody(discoveryauth)
166
167 def test_list_empty(self):
168 self._get('/api/iscsi/target')
169 self.assertStatus(200)
170 self.assertJsonBody([])
171
172 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
173 def test_list(self, _validate_image_mock):
174 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw1"
175 request = copy.deepcopy(iscsi_target_request)
176 request['target_iqn'] = target_iqn
eafe8130 177 self._task_post('/api/iscsi/target', request)
11fdf7f2
TL
178 self.assertStatus(201)
179 self._get('/api/iscsi/target')
180 self.assertStatus(200)
181 response = copy.deepcopy(iscsi_target_response)
182 response['target_iqn'] = target_iqn
183 self.assertJsonBody([response])
184
185 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
186 def test_create(self, _validate_image_mock):
187 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw2"
188 request = copy.deepcopy(iscsi_target_request)
189 request['target_iqn'] = target_iqn
eafe8130 190 self._task_post('/api/iscsi/target', request)
11fdf7f2
TL
191 self.assertStatus(201)
192 self._get('/api/iscsi/target/{}'.format(request['target_iqn']))
193 self.assertStatus(200)
194 response = copy.deepcopy(iscsi_target_response)
195 response['target_iqn'] = target_iqn
196 self.assertJsonBody(response)
197
198 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
199 def test_delete(self, _validate_image_mock):
200 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw3"
201 request = copy.deepcopy(iscsi_target_request)
202 request['target_iqn'] = target_iqn
eafe8130 203 self._task_post('/api/iscsi/target', request)
11fdf7f2 204 self.assertStatus(201)
eafe8130 205 self._task_delete('/api/iscsi/target/{}'.format(request['target_iqn']))
11fdf7f2
TL
206 self.assertStatus(204)
207 self._get('/api/iscsi/target')
208 self.assertStatus(200)
209 self.assertJsonBody([])
210
211 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
212 def test_add_client(self, _validate_image_mock):
213 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw4"
214 create_request = copy.deepcopy(iscsi_target_request)
215 create_request['target_iqn'] = target_iqn
216 update_request = copy.deepcopy(create_request)
217 update_request['new_target_iqn'] = target_iqn
218 update_request['clients'].append(
219 {
220 "luns": [{"image": "lun1", "pool": "rbd"}],
221 "client_iqn": "iqn.1994-05.com.redhat:rh7-client3",
222 "auth": {
223 "password": "myiscsipassword5",
224 "user": "myiscsiusername5",
225 "mutual_password": "myiscsipassword6",
226 "mutual_user": "myiscsiusername6"}
227 })
228 response = copy.deepcopy(iscsi_target_response)
229 response['target_iqn'] = target_iqn
230 response['clients'].append(
231 {
232 "luns": [{"image": "lun1", "pool": "rbd"}],
233 "client_iqn": "iqn.1994-05.com.redhat:rh7-client3",
234 "auth": {
235 "password": "myiscsipassword5",
236 "user": "myiscsiusername5",
237 "mutual_password": "myiscsipassword6",
494da23a
TL
238 "mutual_user": "myiscsiusername6"},
239 "info": {
240 "alias": "",
241 "ip_address": [],
242 "state": {}
243 }
11fdf7f2 244 })
f6b5b4d7 245 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2 246
1911f103
TL
247 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
248 def test_add_bad_client(self, _validate_image_mock):
249 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw4"
250 create_request = copy.deepcopy(iscsi_target_request)
251 create_request['target_iqn'] = target_iqn
252 update_request = copy.deepcopy(create_request)
253 update_request['new_target_iqn'] = target_iqn
254 update_request['clients'].append(
255 {
256 "luns": [{"image": "lun1", "pool": "rbd"}],
257 "client_iqn": "iqn.1994-05.com.redhat:rh7-client4",
258 "auth": {
259 "password": "myiscsipassword7myiscsipassword7myiscsipasswo",
260 "user": "myiscsiusername7",
261 "mutual_password": "myiscsipassword8",
262 "mutual_user": "myiscsiusername8"}
263 })
264 response = copy.deepcopy(iscsi_target_response)
265 response['target_iqn'] = target_iqn
266
267 self._task_post('/api/iscsi/target', create_request)
268 self.assertStatus(201)
269 self._task_put('/api/iscsi/target/{}'.format(create_request['target_iqn']), update_request)
270 self.assertStatus(400)
271 self._get('/api/iscsi/target/{}'.format(update_request['new_target_iqn']))
272 self.assertStatus(200)
273 self.assertJsonBody(response)
274
11fdf7f2
TL
275 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
276 def test_change_client_password(self, _validate_image_mock):
277 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw5"
278 create_request = copy.deepcopy(iscsi_target_request)
279 create_request['target_iqn'] = target_iqn
280 update_request = copy.deepcopy(create_request)
281 update_request['new_target_iqn'] = target_iqn
1911f103 282 update_request['clients'][0]['auth']['password'] = 'MyNewPassword'
11fdf7f2
TL
283 response = copy.deepcopy(iscsi_target_response)
284 response['target_iqn'] = target_iqn
1911f103 285 response['clients'][0]['auth']['password'] = 'MyNewPassword'
f6b5b4d7 286 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2
TL
287
288 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
289 def test_rename_client(self, _validate_image_mock):
290 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw6"
291 create_request = copy.deepcopy(iscsi_target_request)
292 create_request['target_iqn'] = target_iqn
293 update_request = copy.deepcopy(create_request)
294 update_request['new_target_iqn'] = target_iqn
295 update_request['clients'][0]['client_iqn'] = 'iqn.1994-05.com.redhat:rh7-client0'
296 response = copy.deepcopy(iscsi_target_response)
297 response['target_iqn'] = target_iqn
298 response['clients'][0]['client_iqn'] = 'iqn.1994-05.com.redhat:rh7-client0'
f6b5b4d7 299 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2
TL
300
301 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
302 def test_add_disk(self, _validate_image_mock):
303 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw7"
304 create_request = copy.deepcopy(iscsi_target_request)
305 create_request['target_iqn'] = target_iqn
306 update_request = copy.deepcopy(create_request)
307 update_request['new_target_iqn'] = target_iqn
308 update_request['disks'].append(
309 {
310 "image": "lun3",
311 "pool": "rbd",
312 "controls": {},
313 "backstore": "user:rbd"
314 })
315 update_request['clients'][0]['luns'].append({"image": "lun3", "pool": "rbd"})
316 response = copy.deepcopy(iscsi_target_response)
317 response['target_iqn'] = target_iqn
318 response['disks'].append(
319 {
320 "image": "lun3",
321 "pool": "rbd",
322 "controls": {},
eafe8130
TL
323 "backstore": "user:rbd",
324 "wwn": "64af6678-9694-4367-bacc-f8eb0baa2",
325 "lun": 2
326
11fdf7f2
TL
327 })
328 response['clients'][0]['luns'].append({"image": "lun3", "pool": "rbd"})
f6b5b4d7 329 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2
TL
330
331 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
332 def test_change_disk_image(self, _validate_image_mock):
333 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw8"
334 create_request = copy.deepcopy(iscsi_target_request)
335 create_request['target_iqn'] = target_iqn
336 update_request = copy.deepcopy(create_request)
337 update_request['new_target_iqn'] = target_iqn
338 update_request['disks'][0]['image'] = 'lun0'
339 update_request['clients'][0]['luns'][0]['image'] = 'lun0'
340 response = copy.deepcopy(iscsi_target_response)
341 response['target_iqn'] = target_iqn
342 response['disks'][0]['image'] = 'lun0'
343 response['clients'][0]['luns'][0]['image'] = 'lun0'
f6b5b4d7 344 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2
TL
345
346 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
347 def test_change_disk_controls(self, _validate_image_mock):
348 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw9"
349 create_request = copy.deepcopy(iscsi_target_request)
350 create_request['target_iqn'] = target_iqn
351 update_request = copy.deepcopy(create_request)
352 update_request['new_target_iqn'] = target_iqn
353 update_request['disks'][0]['controls'] = {"qfull_timeout": 15}
354 response = copy.deepcopy(iscsi_target_response)
355 response['target_iqn'] = target_iqn
356 response['disks'][0]['controls'] = {"qfull_timeout": 15}
f6b5b4d7 357 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2
TL
358
359 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
360 def test_rename_target(self, _validate_image_mock):
361 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw10"
362 new_target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw11"
363 create_request = copy.deepcopy(iscsi_target_request)
364 create_request['target_iqn'] = target_iqn
365 update_request = copy.deepcopy(create_request)
366 update_request['new_target_iqn'] = new_target_iqn
367 response = copy.deepcopy(iscsi_target_response)
368 response['target_iqn'] = new_target_iqn
f6b5b4d7 369 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2
TL
370
371 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
372 def test_rename_group(self, _validate_image_mock):
373 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw12"
374 create_request = copy.deepcopy(iscsi_target_request)
375 create_request['target_iqn'] = target_iqn
376 update_request = copy.deepcopy(create_request)
377 update_request['new_target_iqn'] = target_iqn
378 update_request['groups'][0]['group_id'] = 'mygroup0'
379 response = copy.deepcopy(iscsi_target_response)
380 response['target_iqn'] = target_iqn
381 response['groups'][0]['group_id'] = 'mygroup0'
f6b5b4d7 382 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2
TL
383
384 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
385 def test_add_client_to_group(self, _validate_image_mock):
386 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw13"
387 create_request = copy.deepcopy(iscsi_target_request)
388 create_request['target_iqn'] = target_iqn
389 update_request = copy.deepcopy(create_request)
390 update_request['new_target_iqn'] = target_iqn
391 update_request['clients'].append(
392 {
393 "luns": [],
394 "client_iqn": "iqn.1994-05.com.redhat:rh7-client3",
395 "auth": {
396 "password": None,
397 "user": None,
398 "mutual_password": None,
399 "mutual_user": None}
400 })
401 update_request['groups'][0]['members'].append('iqn.1994-05.com.redhat:rh7-client3')
402 response = copy.deepcopy(iscsi_target_response)
403 response['target_iqn'] = target_iqn
404 response['clients'].append(
405 {
406 "luns": [],
407 "client_iqn": "iqn.1994-05.com.redhat:rh7-client3",
408 "auth": {
409 "password": None,
410 "user": None,
411 "mutual_password": None,
494da23a
TL
412 "mutual_user": None},
413 "info": {
414 "alias": "",
415 "ip_address": [],
416 "state": {}
417 }
11fdf7f2
TL
418 })
419 response['groups'][0]['members'].append('iqn.1994-05.com.redhat:rh7-client3')
f6b5b4d7 420 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2
TL
421
422 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
423 def test_remove_client_from_group(self, _validate_image_mock):
424 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw14"
425 create_request = copy.deepcopy(iscsi_target_request)
426 create_request['target_iqn'] = target_iqn
427 update_request = copy.deepcopy(create_request)
428 update_request['new_target_iqn'] = target_iqn
429 update_request['groups'][0]['members'].remove('iqn.1994-05.com.redhat:rh7-client2')
430 response = copy.deepcopy(iscsi_target_response)
431 response['target_iqn'] = target_iqn
432 response['groups'][0]['members'].remove('iqn.1994-05.com.redhat:rh7-client2')
f6b5b4d7 433 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2
TL
434
435 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
436 def test_remove_groups(self, _validate_image_mock):
437 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw15"
438 create_request = copy.deepcopy(iscsi_target_request)
439 create_request['target_iqn'] = target_iqn
440 update_request = copy.deepcopy(create_request)
441 update_request['new_target_iqn'] = target_iqn
442 update_request['groups'] = []
443 response = copy.deepcopy(iscsi_target_response)
444 response['target_iqn'] = target_iqn
445 response['groups'] = []
f6b5b4d7 446 self._update_iscsi_target(create_request, update_request, 200, None, response)
11fdf7f2 447
81eedcae
TL
448 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
449 def test_add_client_to_multiple_groups(self, _validate_image_mock):
450 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw16"
451 create_request = copy.deepcopy(iscsi_target_request)
452 create_request['target_iqn'] = target_iqn
453 create_request['groups'].append(copy.deepcopy(create_request['groups'][0]))
454 create_request['groups'][1]['group_id'] = 'mygroup2'
eafe8130 455 self._task_post('/api/iscsi/target', create_request)
81eedcae
TL
456 self.assertStatus(400)
457 self.assertJsonBody({
458 'detail': 'Each initiator can only be part of 1 group at a time',
459 'code': 'initiator_in_multiple_groups',
460 'component': 'iscsi'
461 })
462
f6b5b4d7
TL
463 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
464 def test_remove_client_lun(self, _validate_image_mock):
465 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw17"
466 create_request = copy.deepcopy(iscsi_target_request)
467 create_request['target_iqn'] = target_iqn
468 create_request['clients'][0]['luns'] = [
469 {"image": "lun1", "pool": "rbd"},
470 {"image": "lun2", "pool": "rbd"},
471 {"image": "lun3", "pool": "rbd"}
472 ]
473 update_request = copy.deepcopy(create_request)
474 update_request['new_target_iqn'] = target_iqn
475 update_request['clients'][0]['luns'] = [
476 {"image": "lun1", "pool": "rbd"},
477 {"image": "lun3", "pool": "rbd"}
478 ]
479 response = copy.deepcopy(iscsi_target_response)
480 response['target_iqn'] = target_iqn
481 response['clients'][0]['luns'] = [
482 {"image": "lun1", "pool": "rbd"},
483 {"image": "lun3", "pool": "rbd"}
484 ]
485 self._update_iscsi_target(create_request, update_request, 200, None, response)
486
487 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
488 def test_change_client_auth(self, _validate_image_mock):
489 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw18"
490 create_request = copy.deepcopy(iscsi_target_request)
491 create_request['target_iqn'] = target_iqn
492 update_request = copy.deepcopy(create_request)
493 update_request['new_target_iqn'] = target_iqn
494 update_request['clients'][0]['auth']['password'] = 'myiscsipasswordX'
495 response = copy.deepcopy(iscsi_target_response)
496 response['target_iqn'] = target_iqn
497 response['clients'][0]['auth']['password'] = 'myiscsipasswordX'
498 self._update_iscsi_target(create_request, update_request, 200, None, response)
499
500 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
501 def test_remove_client_logged_in(self, _validate_image_mock):
502 client_info = {
503 'alias': '',
504 'ip_address': [],
505 'state': {'LOGGED_IN': ['node1']}
506 }
507 # pylint: disable=protected-access
508 IscsiClientMock._instance.clientinfo = client_info
509 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw19"
510 create_request = copy.deepcopy(iscsi_target_request)
511 create_request['target_iqn'] = target_iqn
512 update_request = copy.deepcopy(create_request)
513 update_request['new_target_iqn'] = target_iqn
514 update_request['clients'].pop(0)
515 response = copy.deepcopy(iscsi_target_response)
516 response['target_iqn'] = target_iqn
517 for client in response['clients']:
518 client['info'] = client_info
519 update_response = {
520 'detail': "Client 'iqn.1994-05.com.redhat:rh7-client' cannot be deleted until it's "
521 "logged out",
522 'code': 'client_logged_in',
523 'component': 'iscsi'
524 }
525 self._update_iscsi_target(create_request, update_request, 400, update_response, response)
526
527 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
528 def test_remove_client(self, _validate_image_mock):
529 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw20"
530 create_request = copy.deepcopy(iscsi_target_request)
531 create_request['target_iqn'] = target_iqn
532 update_request = copy.deepcopy(create_request)
533 update_request['new_target_iqn'] = target_iqn
534 update_request['clients'].pop(0)
535 response = copy.deepcopy(iscsi_target_response)
536 response['target_iqn'] = target_iqn
537 response['clients'].pop(0)
538 self._update_iscsi_target(create_request, update_request, 200, None, response)
539
f91f0fd5
TL
540 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
541 def test_add_image_to_group_with_client_logged_in(self, _validate_image_mock):
542 client_info = {
543 'alias': '',
544 'ip_address': [],
545 'state': {'LOGGED_IN': ['node1']}
546 }
547 new_disk = {"pool": "rbd", "image": "lun1"}
548 # pylint: disable=protected-access
549 IscsiClientMock._instance.clientinfo = client_info
550 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw21"
551 create_request = copy.deepcopy(iscsi_target_request)
552 create_request['target_iqn'] = target_iqn
553 update_request = copy.deepcopy(create_request)
554 update_request['new_target_iqn'] = target_iqn
555 update_request['groups'][0]['disks'].append(new_disk)
556 response = copy.deepcopy(iscsi_target_response)
557 response['target_iqn'] = target_iqn
558 response['groups'][0]['disks'].insert(0, new_disk)
559 for client in response['clients']:
560 client['info'] = client_info
561 self._update_iscsi_target(create_request, update_request, 200, None, response)
562
563 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
564 def test_add_image_to_initiator_with_client_logged_in(self, _validate_image_mock):
565 client_info = {
566 'alias': '',
567 'ip_address': [],
568 'state': {'LOGGED_IN': ['node1']}
569 }
570 new_disk = {"pool": "rbd", "image": "lun2"}
571 # pylint: disable=protected-access
572 IscsiClientMock._instance.clientinfo = client_info
573 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw22"
574 create_request = copy.deepcopy(iscsi_target_request)
575 create_request['target_iqn'] = target_iqn
576 update_request = copy.deepcopy(create_request)
577 update_request['new_target_iqn'] = target_iqn
578 update_request['clients'][0]['luns'].append(new_disk)
579 response = copy.deepcopy(iscsi_target_response)
580 response['target_iqn'] = target_iqn
581 response['clients'][0]['luns'].append(new_disk)
582 for client in response['clients']:
583 client['info'] = client_info
584 self._update_iscsi_target(create_request, update_request, 200, None, response)
585
586 @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
587 def test_remove_image_from_group_with_client_logged_in(self, _validate_image_mock):
588 client_info = {
589 'alias': '',
590 'ip_address': [],
591 'state': {'LOGGED_IN': ['node1']}
592 }
593 # pylint: disable=protected-access
594 IscsiClientMock._instance.clientinfo = client_info
595 target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw23"
596 create_request = copy.deepcopy(iscsi_target_request)
597 create_request['target_iqn'] = target_iqn
598 update_request = copy.deepcopy(create_request)
599 update_request['new_target_iqn'] = target_iqn
600 update_request['groups'][0]['disks'] = []
601 response = copy.deepcopy(iscsi_target_response)
602 response['target_iqn'] = target_iqn
603 response['groups'][0]['disks'] = []
604 for client in response['clients']:
605 client['info'] = client_info
606 self._update_iscsi_target(create_request, update_request, 200, None, response)
607
f6b5b4d7
TL
608 def _update_iscsi_target(self, create_request, update_request, update_response_code,
609 update_response, response):
eafe8130 610 self._task_post('/api/iscsi/target', create_request)
11fdf7f2 611 self.assertStatus(201)
f67539c2
TL
612 self._task_put(
613 '/api/iscsi/target/{}'.format(create_request['target_iqn']), update_request)
f6b5b4d7
TL
614 self.assertStatus(update_response_code)
615 self.assertJsonBody(update_response)
f67539c2
TL
616 self._get(
617 '/api/iscsi/target/{}'.format(update_request['new_target_iqn']))
11fdf7f2
TL
618 self.assertStatus(200)
619 self.assertJsonBody(response)
620
621
622iscsi_target_request = {
623 "target_iqn": "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw",
624 "portals": [
625 {"ip": "192.168.100.202", "host": "node2"},
626 {"ip": "10.0.2.15", "host": "node2"},
627 {"ip": "192.168.100.203", "host": "node3"}
628 ],
629 "disks": [
630 {"image": "lun1", "pool": "rbd", "backstore": "user:rbd",
631 "controls": {"max_data_area_mb": 128}},
632 {"image": "lun2", "pool": "rbd", "backstore": "user:rbd",
633 "controls": {"max_data_area_mb": 128}}
634 ],
635 "clients": [
636 {
637 "luns": [{"image": "lun1", "pool": "rbd"}],
638 "client_iqn": "iqn.1994-05.com.redhat:rh7-client",
639 "auth": {
640 "password": "myiscsipassword1",
641 "user": "myiscsiusername1",
642 "mutual_password": "myiscsipassword2",
643 "mutual_user": "myiscsiusername2"}
644 },
645 {
646 "luns": [],
647 "client_iqn": "iqn.1994-05.com.redhat:rh7-client2",
648 "auth": {
649 "password": "myiscsipassword3",
650 "user": "myiscsiusername3",
651 "mutual_password": "myiscsipassword4",
652 "mutual_user": "myiscsiusername4"
653 }
654 }
655 ],
656 "acl_enabled": True,
eafe8130
TL
657 "auth": {
658 "password": "",
659 "user": "",
660 "mutual_password": "",
661 "mutual_user": ""},
11fdf7f2
TL
662 "target_controls": {},
663 "groups": [
664 {
665 "group_id": "mygroup",
666 "disks": [{"pool": "rbd", "image": "lun2"}],
667 "members": ["iqn.1994-05.com.redhat:rh7-client2"]
668 }
669 ]
670}
671
672iscsi_target_response = {
673 'target_iqn': 'iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw',
674 'portals': [
675 {'host': 'node2', 'ip': '10.0.2.15'},
676 {'host': 'node2', 'ip': '192.168.100.202'},
677 {'host': 'node3', 'ip': '192.168.100.203'}
678 ],
679 'disks': [
680 {'pool': 'rbd', 'image': 'lun1', 'backstore': 'user:rbd',
eafe8130 681 'wwn': '64af6678-9694-4367-bacc-f8eb0baa0', 'lun': 0,
11fdf7f2
TL
682 'controls': {'max_data_area_mb': 128}},
683 {'pool': 'rbd', 'image': 'lun2', 'backstore': 'user:rbd',
eafe8130 684 'wwn': '64af6678-9694-4367-bacc-f8eb0baa1', 'lun': 1,
11fdf7f2
TL
685 'controls': {'max_data_area_mb': 128}}
686 ],
687 'clients': [
688 {
689 'client_iqn': 'iqn.1994-05.com.redhat:rh7-client',
690 'luns': [{'pool': 'rbd', 'image': 'lun1'}],
691 'auth': {
692 'user': 'myiscsiusername1',
693 'password': 'myiscsipassword1',
694 'mutual_password': 'myiscsipassword2',
695 'mutual_user': 'myiscsiusername2'
494da23a
TL
696 },
697 'info': {
698 'alias': '',
699 'ip_address': [],
700 'state': {}
11fdf7f2
TL
701 }
702 },
703 {
704 'client_iqn': 'iqn.1994-05.com.redhat:rh7-client2',
705 'luns': [],
706 'auth': {
707 'user': 'myiscsiusername3',
708 'password': 'myiscsipassword3',
709 'mutual_password': 'myiscsipassword4',
710 'mutual_user': 'myiscsiusername4'
494da23a
TL
711 },
712 'info': {
713 'alias': '',
714 'ip_address': [],
715 'state': {}
11fdf7f2
TL
716 }
717 }
718 ],
719 "acl_enabled": True,
eafe8130
TL
720 "auth": {
721 "password": "",
722 "user": "",
723 "mutual_password": "",
724 "mutual_user": ""},
11fdf7f2
TL
725 'groups': [
726 {
727 'group_id': 'mygroup',
728 'disks': [{'pool': 'rbd', 'image': 'lun2'}],
729 'members': ['iqn.1994-05.com.redhat:rh7-client2']
730 }
731 ],
732 'target_controls': {},
733 'info': {
734 'num_sessions': 0
735 }
736}
737
738
739class IscsiClientMock(object):
740
741 _instance = None
742
743 def __init__(self):
744 self.gateway_name = None
745 self.service_url = None
746 self.config = {
747 "created": "2019/01/17 08:57:16",
748 "discovery_auth": {
749 "username": "",
750 "password": "",
751 "password_encryption_enabled": False,
752 "mutual_username": "",
753 "mutual_password": "",
754 "mutual_password_encryption_enabled": False
755 },
756 "disks": {},
757 "epoch": 0,
758 "gateways": {},
759 "targets": {},
760 "updated": "",
eafe8130 761 "version": 11
11fdf7f2 762 }
f6b5b4d7
TL
763 self.clientinfo = {
764 'alias': '',
765 'ip_address': [],
766 'state': {}
767 }
11fdf7f2
TL
768
769 @classmethod
770 def instance(cls, gateway_name=None, service_url=None):
771 cls._instance.gateway_name = gateway_name
772 cls._instance.service_url = service_url
773 # pylint: disable=unused-argument
774 return cls._instance
775
776 def ping(self):
777 return {
778 "message": "pong"
779 }
780
781 def get_settings(self):
782 return {
1911f103 783 "api_version": 2,
11fdf7f2
TL
784 "backstores": [
785 "user:rbd"
786 ],
787 "config": {
788 "minimum_gateways": 2
789 },
790 "default_backstore": "user:rbd",
791 "required_rbd_features": {
792 "rbd": 0,
793 "user:rbd": 4,
794 },
81eedcae
TL
795 "unsupported_rbd_features": {
796 "rbd": 88,
797 "user:rbd": 0,
11fdf7f2
TL
798 },
799 "disk_default_controls": {
800 "user:rbd": {
801 "hw_max_sectors": 1024,
802 "max_data_area_mb": 8,
803 "osd_op_timeout": 30,
804 "qfull_timeout": 5
805 }
806 },
807 "target_default_controls": {
808 "cmdsn_depth": 128,
809 "dataout_timeout": 20,
810 "first_burst_length": 262144,
811 "immediate_data": "Yes",
812 "initial_r2t": "Yes",
813 "max_burst_length": 524288,
814 "max_outstanding_r2t": 1,
815 "max_recv_data_segment_length": 262144,
816 "max_xmit_data_segment_length": 262144,
817 "nopin_response_timeout": 5,
818 "nopin_timeout": 5
819 }
820 }
821
822 def get_config(self):
eafe8130 823 return copy.deepcopy(self.config)
11fdf7f2
TL
824
825 def create_target(self, target_iqn, target_controls):
826 self.config['targets'][target_iqn] = {
827 "clients": {},
828 "acl_enabled": True,
eafe8130
TL
829 "auth": {
830 "username": "",
831 "password": "",
832 "password_encryption_enabled": False,
833 "mutual_username": "",
834 "mutual_password": "",
835 "mutual_password_encryption_enabled": False
836 },
11fdf7f2
TL
837 "controls": target_controls,
838 "created": "2019/01/17 09:22:34",
eafe8130 839 "disks": {},
11fdf7f2
TL
840 "groups": {},
841 "portals": {}
842 }
843
494da23a 844 def create_gateway(self, target_iqn, gateway_name, ip_addresses):
11fdf7f2
TL
845 target_config = self.config['targets'][target_iqn]
846 if 'ip_list' not in target_config:
847 target_config['ip_list'] = []
494da23a 848 target_config['ip_list'] += ip_addresses
11fdf7f2 849 target_config['portals'][gateway_name] = {
494da23a 850 "portal_ip_addresses": ip_addresses
11fdf7f2
TL
851 }
852
494da23a
TL
853 def delete_gateway(self, target_iqn, gateway_name):
854 target_config = self.config['targets'][target_iqn]
855 portal_config = target_config['portals'][gateway_name]
856 for ip in portal_config['portal_ip_addresses']:
857 target_config['ip_list'].remove(ip)
858 target_config['portals'].pop(gateway_name)
859
eafe8130
TL
860 def create_disk(self, pool, image, backstore, wwn):
861 if wwn is None:
862 wwn = '64af6678-9694-4367-bacc-f8eb0baa' + str(len(self.config['disks']))
11fdf7f2
TL
863 image_id = '{}/{}'.format(pool, image)
864 self.config['disks'][image_id] = {
865 "pool": pool,
866 "image": image,
867 "backstore": backstore,
eafe8130
TL
868 "controls": {},
869 "wwn": wwn
11fdf7f2
TL
870 }
871
eafe8130 872 def create_target_lun(self, target_iqn, image_id, lun):
11fdf7f2 873 target_config = self.config['targets'][target_iqn]
eafe8130
TL
874 if lun is None:
875 lun = len(target_config['disks'])
876 target_config['disks'][image_id] = {
877 "lun_id": lun
878 }
11fdf7f2
TL
879 self.config['disks'][image_id]['owner'] = list(target_config['portals'].keys())[0]
880
881 def reconfigure_disk(self, pool, image, controls):
882 image_id = '{}/{}'.format(pool, image)
eafe8130
TL
883 settings = self.get_settings()
884 backstore = self.config['disks'][image_id]['backstore']
885 disk_default_controls = settings['disk_default_controls'][backstore]
886 new_controls = {}
887 for control_k, control_v in controls.items():
888 if control_v != disk_default_controls[control_k]:
889 new_controls[control_k] = control_v
890 self.config['disks'][image_id]['controls'] = new_controls
11fdf7f2
TL
891
892 def create_client(self, target_iqn, client_iqn):
893 target_config = self.config['targets'][target_iqn]
894 target_config['clients'][client_iqn] = {
895 "auth": {
896 "username": "",
897 "password": "",
898 "password_encryption_enabled": False,
899 "mutual_username": "",
900 "mutual_password": "",
901 "mutual_password_encryption_enabled": False
902 },
903 "group_name": "",
904 "luns": {}
905 }
906
907 def create_client_lun(self, target_iqn, client_iqn, image_id):
908 target_config = self.config['targets'][target_iqn]
909 target_config['clients'][client_iqn]['luns'][image_id] = {}
910
eafe8130
TL
911 def delete_client_lun(self, target_iqn, client_iqn, image_id):
912 target_config = self.config['targets'][target_iqn]
913 del target_config['clients'][client_iqn]['luns'][image_id]
914
11fdf7f2
TL
915 def create_client_auth(self, target_iqn, client_iqn, user, password, m_user, m_password):
916 target_config = self.config['targets'][target_iqn]
917 target_config['clients'][client_iqn]['auth']['username'] = user
918 target_config['clients'][client_iqn]['auth']['password'] = password
919 target_config['clients'][client_iqn]['auth']['mutual_username'] = m_user
920 target_config['clients'][client_iqn]['auth']['mutual_password'] = m_password
921
922 def create_group(self, target_iqn, group_name, members, image_ids):
923 target_config = self.config['targets'][target_iqn]
924 target_config['groups'][group_name] = {
925 "disks": {},
926 "members": []
927 }
928 for image_id in image_ids:
929 target_config['groups'][group_name]['disks'][image_id] = {}
930 target_config['groups'][group_name]['members'] = members
931
f91f0fd5
TL
932 def update_group(self, target_iqn, group_name, members, image_ids):
933 target_config = self.config['targets'][target_iqn]
934 group = target_config['groups'][group_name]
935 old_members = group['members']
936 disks = group['disks']
937 target_config['groups'][group_name] = {
938 "disks": {},
939 "members": []
940 }
941
942 for image_id in disks.keys():
943 if image_id not in image_ids:
944 target_config['groups'][group_name]['disks'][image_id] = {}
945
946 new_members = []
947 for member_iqn in old_members:
948 if member_iqn not in members:
949 new_members.append(member_iqn)
950 target_config['groups'][group_name]['members'] = new_members
951
11fdf7f2
TL
952 def delete_group(self, target_iqn, group_name):
953 target_config = self.config['targets'][target_iqn]
954 del target_config['groups'][group_name]
955
956 def delete_client(self, target_iqn, client_iqn):
957 target_config = self.config['targets'][target_iqn]
958 del target_config['clients'][client_iqn]
959
960 def delete_target_lun(self, target_iqn, image_id):
961 target_config = self.config['targets'][target_iqn]
eafe8130 962 target_config['disks'].pop(image_id)
11fdf7f2
TL
963 del self.config['disks'][image_id]['owner']
964
965 def delete_disk(self, pool, image):
966 image_id = '{}/{}'.format(pool, image)
967 del self.config['disks'][image_id]
968
969 def delete_target(self, target_iqn):
970 del self.config['targets'][target_iqn]
971
972 def get_ip_addresses(self):
973 ips = {
974 'node1': ['192.168.100.201'],
975 'node2': ['192.168.100.202', '10.0.2.15'],
976 'node3': ['192.168.100.203']
977 }
978 return {'data': ips[self.gateway_name]}
979
980 def get_hostname(self):
981 hostnames = {
982 'https://admin:admin@10.17.5.1:5001': 'node1',
983 'https://admin:admin@10.17.5.2:5001': 'node2',
984 'https://admin:admin@10.17.5.3:5001': 'node3'
985 }
986 if self.service_url not in hostnames:
987 raise RequestException('No route to host')
988 return {'data': hostnames[self.service_url]}
989
990 def update_discoveryauth(self, user, password, mutual_user, mutual_password):
991 self.config['discovery_auth']['username'] = user
992 self.config['discovery_auth']['password'] = password
993 self.config['discovery_auth']['mutual_username'] = mutual_user
994 self.config['discovery_auth']['mutual_password'] = mutual_password
995
eafe8130 996 def update_targetacl(self, target_iqn, action):
11fdf7f2
TL
997 self.config['targets'][target_iqn]['acl_enabled'] = (action == 'enable_acl')
998
eafe8130
TL
999 def update_targetauth(self, target_iqn, user, password, mutual_user, mutual_password):
1000 target_config = self.config['targets'][target_iqn]
1001 target_config['auth']['username'] = user
1002 target_config['auth']['password'] = password
1003 target_config['auth']['mutual_username'] = mutual_user
1004 target_config['auth']['mutual_password'] = mutual_password
1005
494da23a
TL
1006 def get_targetinfo(self, target_iqn):
1007 # pylint: disable=unused-argument
11fdf7f2
TL
1008 return {
1009 'num_sessions': 0
1010 }
494da23a
TL
1011
1012 def get_clientinfo(self, target_iqn, client_iqn):
1013 # pylint: disable=unused-argument
f6b5b4d7 1014 return self.clientinfo