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