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