]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/controllers/iscsi.py
import 14.2.4 nautilus point release
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / iscsi.py
CommitLineData
11fdf7f2
TL
1# -*- coding: utf-8 -*-
2# pylint: disable=too-many-branches
3from __future__ import absolute_import
4
5from copy import deepcopy
6import json
7import cherrypy
8
9import rados
10import rbd
11
12from . import ApiController, UiApiController, RESTController, BaseController, Endpoint,\
13 ReadPermission, UpdatePermission, Task
14from .. import mgr
15from ..rest_client import RequestException
16from ..security import Scope
17from ..services.iscsi_client import IscsiClient
18from ..services.iscsi_cli import IscsiGatewaysConfig
19from ..services.rbd import format_bitmask
20from ..services.tcmu_service import TcmuService
21from ..exceptions import DashboardException
22from ..tools import TaskManager
23
24
25@UiApiController('/iscsi', Scope.ISCSI)
26class IscsiUi(BaseController):
27
494da23a 28 REQUIRED_CEPH_ISCSI_CONFIG_VERSION = 10
11fdf7f2
TL
29
30 @Endpoint()
31 @ReadPermission
32 def status(self):
33 status = {'available': False}
34 gateways = IscsiGatewaysConfig.get_gateways_config()['gateways']
35 if not gateways:
36 status['message'] = 'There are no gateways defined'
37 return status
38 try:
81eedcae 39 for gateway in gateways:
11fdf7f2
TL
40 try:
41 IscsiClient.instance(gateway_name=gateway).ping()
42 except RequestException:
43 status['message'] = 'Gateway {} is inaccessible'.format(gateway)
44 return status
45 config = IscsiClient.instance().get_config()
46 if config['version'] != IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_VERSION:
47 status['message'] = 'Unsupported `ceph-iscsi` config version. Expected {} but ' \
48 'found {}.'.format(IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_VERSION,
49 config['version'])
50 return status
51 status['available'] = True
52 except RequestException as e:
53 if e.content:
81eedcae
TL
54 try:
55 content = json.loads(e.content)
56 content_message = content.get('message')
57 except ValueError:
58 content_message = e.content
11fdf7f2
TL
59 if content_message:
60 status['message'] = content_message
81eedcae 61
11fdf7f2
TL
62 return status
63
64 @Endpoint()
65 @ReadPermission
66 def settings(self):
67 return IscsiClient.instance().get_settings()
68
69 @Endpoint()
70 @ReadPermission
71 def portals(self):
72 portals = []
73 gateways_config = IscsiGatewaysConfig.get_gateways_config()
81eedcae 74 for name in gateways_config['gateways']:
11fdf7f2
TL
75 ip_addresses = IscsiClient.instance(gateway_name=name).get_ip_addresses()
76 portals.append({'name': name, 'ip_addresses': ip_addresses['data']})
77 return sorted(portals, key=lambda p: '{}.{}'.format(p['name'], p['ip_addresses']))
78
79 @Endpoint()
80 @ReadPermission
81 def overview(self):
82 result_gateways = []
83 result_images = []
84 gateways_names = IscsiGatewaysConfig.get_gateways_config()['gateways'].keys()
85 config = None
86 for gateway_name in gateways_names:
87 try:
88 config = IscsiClient.instance(gateway_name=gateway_name).get_config()
89 break
90 except RequestException:
91 pass
92
93 # Gateways info
94 for gateway_name in gateways_names:
95 gateway = {
96 'name': gateway_name,
97 'state': '',
98 'num_targets': 'n/a',
99 'num_sessions': 'n/a'
100 }
101 try:
102 IscsiClient.instance(gateway_name=gateway_name).ping()
103 gateway['state'] = 'up'
104 if config:
105 gateway['num_sessions'] = 0
106 if gateway_name in config['gateways']:
107 gatewayinfo = IscsiClient.instance(
108 gateway_name=gateway_name).get_gatewayinfo()
109 gateway['num_sessions'] = gatewayinfo['num_sessions']
110 except RequestException:
111 gateway['state'] = 'down'
112 if config:
113 gateway['num_targets'] = len([target for _, target in config['targets'].items()
114 if gateway_name in target['portals']])
115 result_gateways.append(gateway)
116
117 # Images info
118 if config:
119 tcmu_info = TcmuService.get_iscsi_info()
120 for _, disk_config in config['disks'].items():
121 image = {
122 'pool': disk_config['pool'],
123 'image': disk_config['image'],
124 'backstore': disk_config['backstore'],
125 'optimized_since': None,
126 'stats': None,
127 'stats_history': None
128 }
129 tcmu_image_info = TcmuService.get_image_info(image['pool'],
130 image['image'],
131 tcmu_info)
132 if tcmu_image_info:
133 if 'optimized_since' in tcmu_image_info:
134 image['optimized_since'] = tcmu_image_info['optimized_since']
135 if 'stats' in tcmu_image_info:
136 image['stats'] = tcmu_image_info['stats']
137 if 'stats_history' in tcmu_image_info:
138 image['stats_history'] = tcmu_image_info['stats_history']
139 result_images.append(image)
140
141 return {
142 'gateways': sorted(result_gateways, key=lambda g: g['name']),
143 'images': sorted(result_images, key=lambda i: '{}/{}'.format(i['pool'], i['image']))
144 }
145
146
147@ApiController('/iscsi', Scope.ISCSI)
148class Iscsi(BaseController):
149
150 @Endpoint('GET', 'discoveryauth')
81eedcae 151 @ReadPermission
11fdf7f2
TL
152 def get_discoveryauth(self):
153 return self._get_discoveryauth()
154
155 @Endpoint('PUT', 'discoveryauth')
156 @UpdatePermission
157 def set_discoveryauth(self, user, password, mutual_user, mutual_password):
158 IscsiClient.instance().update_discoveryauth(user, password, mutual_user, mutual_password)
159 return self._get_discoveryauth()
160
161 def _get_discoveryauth(self):
162 config = IscsiClient.instance().get_config()
163 user = config['discovery_auth']['username']
164 password = config['discovery_auth']['password']
165 mutual_user = config['discovery_auth']['mutual_username']
166 mutual_password = config['discovery_auth']['mutual_password']
167 return {
168 'user': user,
169 'password': password,
170 'mutual_user': mutual_user,
171 'mutual_password': mutual_password
172 }
173
174
175def iscsi_target_task(name, metadata, wait_for=2.0):
176 return Task("iscsi/target/{}".format(name), metadata, wait_for)
177
178
179@ApiController('/iscsi/target', Scope.ISCSI)
180class IscsiTarget(RESTController):
181
182 def list(self):
183 config = IscsiClient.instance().get_config()
184 targets = []
185 for target_iqn in config['targets'].keys():
186 target = IscsiTarget._config_to_target(target_iqn, config)
187 IscsiTarget._set_info(target)
188 targets.append(target)
189 return targets
190
191 def get(self, target_iqn):
192 config = IscsiClient.instance().get_config()
193 if target_iqn not in config['targets']:
194 raise cherrypy.HTTPError(404)
195 target = IscsiTarget._config_to_target(target_iqn, config)
196 IscsiTarget._set_info(target)
197 return target
198
199 @iscsi_target_task('delete', {'target_iqn': '{target_iqn}'})
200 def delete(self, target_iqn):
201 config = IscsiClient.instance().get_config()
202 if target_iqn not in config['targets']:
203 raise DashboardException(msg='Target does not exist',
204 code='target_does_not_exist',
205 component='iscsi')
206 if target_iqn not in config['targets']:
207 raise DashboardException(msg='Target does not exist',
208 code='target_does_not_exist',
209 component='iscsi')
210 IscsiTarget._delete(target_iqn, config, 0, 100)
211
212 @iscsi_target_task('create', {'target_iqn': '{target_iqn}'})
213 def create(self, target_iqn=None, target_controls=None, acl_enabled=None,
214 portals=None, disks=None, clients=None, groups=None):
215 target_controls = target_controls or {}
216 portals = portals or []
217 disks = disks or []
218 clients = clients or []
219 groups = groups or []
220
221 config = IscsiClient.instance().get_config()
222 if target_iqn in config['targets']:
223 raise DashboardException(msg='Target already exists',
224 code='target_already_exists',
225 component='iscsi')
81eedcae 226 IscsiTarget._validate(target_iqn, portals, disks, groups)
11fdf7f2
TL
227 IscsiTarget._create(target_iqn, target_controls, acl_enabled, portals, disks, clients,
228 groups, 0, 100, config)
229
230 @iscsi_target_task('edit', {'target_iqn': '{target_iqn}'})
231 def set(self, target_iqn, new_target_iqn=None, target_controls=None, acl_enabled=None,
232 portals=None, disks=None, clients=None, groups=None):
233 target_controls = target_controls or {}
234 portals = IscsiTarget._sorted_portals(portals)
235 disks = IscsiTarget._sorted_disks(disks)
236 clients = IscsiTarget._sorted_clients(clients)
237 groups = IscsiTarget._sorted_groups(groups)
238
239 config = IscsiClient.instance().get_config()
240 if target_iqn not in config['targets']:
241 raise DashboardException(msg='Target does not exist',
242 code='target_does_not_exist',
243 component='iscsi')
244 if target_iqn != new_target_iqn and new_target_iqn in config['targets']:
245 raise DashboardException(msg='Target IQN already in use',
246 code='target_iqn_already_in_use',
247 component='iscsi')
81eedcae 248 IscsiTarget._validate(new_target_iqn, portals, disks, groups)
11fdf7f2
TL
249 config = IscsiTarget._delete(target_iqn, config, 0, 50, new_target_iqn, target_controls,
250 portals, disks, clients, groups)
251 IscsiTarget._create(new_target_iqn, target_controls, acl_enabled, portals, disks, clients,
252 groups, 50, 100, config)
253
254 @staticmethod
255 def _delete(target_iqn, config, task_progress_begin, task_progress_end, new_target_iqn=None,
256 new_target_controls=None, new_portals=None, new_disks=None, new_clients=None,
257 new_groups=None):
258 new_target_controls = new_target_controls or {}
259 new_portals = new_portals or []
260 new_disks = new_disks or []
261 new_clients = new_clients or []
262 new_groups = new_groups or []
263
264 TaskManager.current_task().set_progress(task_progress_begin)
265 target_config = config['targets'][target_iqn]
266 if not target_config['portals'].keys():
267 raise DashboardException(msg="Cannot delete a target that doesn't contain any portal",
268 code='cannot_delete_target_without_portals',
269 component='iscsi')
270 target = IscsiTarget._config_to_target(target_iqn, config)
271 n_groups = len(target_config['groups'])
272 n_clients = len(target_config['clients'])
273 n_target_disks = len(target_config['disks'])
274 task_progress_steps = n_groups + n_clients + n_target_disks
275 task_progress_inc = 0
276 if task_progress_steps != 0:
277 task_progress_inc = int((task_progress_end - task_progress_begin) / task_progress_steps)
278 gateway_name = list(target_config['portals'].keys())[0]
279 deleted_groups = []
280 for group_id in list(target_config['groups'].keys()):
281 if IscsiTarget._group_deletion_required(target, new_target_iqn, new_target_controls,
494da23a 282 new_groups, group_id, new_clients,
11fdf7f2
TL
283 new_disks):
284 deleted_groups.append(group_id)
285 IscsiClient.instance(gateway_name=gateway_name).delete_group(target_iqn,
286 group_id)
287 TaskManager.current_task().inc_progress(task_progress_inc)
288 for client_iqn in list(target_config['clients'].keys()):
289 if IscsiTarget._client_deletion_required(target, new_target_iqn, new_target_controls,
494da23a 290 new_clients, client_iqn,
11fdf7f2
TL
291 new_groups, deleted_groups):
292 IscsiClient.instance(gateway_name=gateway_name).delete_client(target_iqn,
293 client_iqn)
294 TaskManager.current_task().inc_progress(task_progress_inc)
295 for image_id in target_config['disks']:
296 if IscsiTarget._target_lun_deletion_required(target, new_target_iqn,
494da23a 297 new_target_controls,
11fdf7f2
TL
298 new_disks, image_id):
299 IscsiClient.instance(gateway_name=gateway_name).delete_target_lun(target_iqn,
300 image_id)
301 pool, image = image_id.split('/', 1)
302 IscsiClient.instance(gateway_name=gateway_name).delete_disk(pool, image)
303 TaskManager.current_task().inc_progress(task_progress_inc)
494da23a
TL
304 old_portals_by_host = IscsiTarget._get_portals_by_host(target['portals'])
305 new_portals_by_host = IscsiTarget._get_portals_by_host(new_portals)
306 for old_portal_host, old_portal_ip_list in old_portals_by_host.items():
307 if IscsiTarget._target_portal_deletion_required(old_portal_host,
308 old_portal_ip_list,
309 new_portals_by_host):
310 IscsiClient.instance(gateway_name=gateway_name).delete_gateway(target_iqn,
311 old_portal_host)
312 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
11fdf7f2
TL
313 IscsiClient.instance(gateway_name=gateway_name).delete_target(target_iqn)
314 TaskManager.current_task().set_progress(task_progress_end)
315 return IscsiClient.instance(gateway_name=gateway_name).get_config()
316
317 @staticmethod
318 def _get_group(groups, group_id):
319 for group in groups:
320 if group['group_id'] == group_id:
321 return group
322 return None
323
324 @staticmethod
494da23a 325 def _group_deletion_required(target, new_target_iqn, new_target_controls,
11fdf7f2 326 new_groups, group_id, new_clients, new_disks):
494da23a 327 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
11fdf7f2
TL
328 return True
329 new_group = IscsiTarget._get_group(new_groups, group_id)
330 if not new_group:
331 return True
332 old_group = IscsiTarget._get_group(target['groups'], group_id)
333 if new_group != old_group:
334 return True
335 # Check if any client inside this group has changed
336 for client_iqn in new_group['members']:
337 if IscsiTarget._client_deletion_required(target, new_target_iqn, new_target_controls,
494da23a 338 new_clients, client_iqn,
11fdf7f2
TL
339 new_groups, []):
340 return True
341 # Check if any disk inside this group has changed
342 for disk in new_group['disks']:
343 image_id = '{}/{}'.format(disk['pool'], disk['image'])
344 if IscsiTarget._target_lun_deletion_required(target, new_target_iqn,
494da23a 345 new_target_controls,
11fdf7f2
TL
346 new_disks, image_id):
347 return True
348 return False
349
350 @staticmethod
351 def _get_client(clients, client_iqn):
352 for client in clients:
353 if client['client_iqn'] == client_iqn:
354 return client
355 return None
356
357 @staticmethod
494da23a 358 def _client_deletion_required(target, new_target_iqn, new_target_controls,
11fdf7f2 359 new_clients, client_iqn, new_groups, deleted_groups):
494da23a 360 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
11fdf7f2
TL
361 return True
362 new_client = deepcopy(IscsiTarget._get_client(new_clients, client_iqn))
363 if not new_client:
364 return True
365 # Disks inherited from groups must be considered
366 for group in new_groups:
367 if client_iqn in group['members']:
368 new_client['luns'] += group['disks']
369 old_client = IscsiTarget._get_client(target['clients'], client_iqn)
370 if new_client != old_client:
371 return True
372 # Check if client belongs to a groups that has been deleted
373 for group in target['groups']:
374 if group['group_id'] in deleted_groups and client_iqn in group['members']:
375 return True
376 return False
377
378 @staticmethod
379 def _get_disk(disks, image_id):
380 for disk in disks:
381 if '{}/{}'.format(disk['pool'], disk['image']) == image_id:
382 return disk
383 return None
384
385 @staticmethod
494da23a 386 def _target_lun_deletion_required(target, new_target_iqn, new_target_controls,
11fdf7f2 387 new_disks, image_id):
494da23a 388 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
11fdf7f2
TL
389 return True
390 new_disk = IscsiTarget._get_disk(new_disks, image_id)
391 if not new_disk:
392 return True
393 old_disk = IscsiTarget._get_disk(target['disks'], image_id)
394 if new_disk != old_disk:
395 return True
396 return False
397
398 @staticmethod
494da23a
TL
399 def _target_portal_deletion_required(old_portal_host, old_portal_ip_list, new_portals_by_host):
400 if old_portal_host not in new_portals_by_host:
401 return True
402 if sorted(old_portal_ip_list) != sorted(new_portals_by_host[old_portal_host]):
403 return True
404 return False
405
406 @staticmethod
407 def _target_deletion_required(target, new_target_iqn, new_target_controls):
11fdf7f2
TL
408 if target['target_iqn'] != new_target_iqn:
409 return True
410 if target['target_controls'] != new_target_controls:
411 return True
11fdf7f2
TL
412 return False
413
414 @staticmethod
81eedcae 415 def _validate(target_iqn, portals, disks, groups):
11fdf7f2
TL
416 if not target_iqn:
417 raise DashboardException(msg='Target IQN is required',
418 code='target_iqn_required',
419 component='iscsi')
420
421 settings = IscsiClient.instance().get_settings()
422 minimum_gateways = max(1, settings['config']['minimum_gateways'])
423 portals_by_host = IscsiTarget._get_portals_by_host(portals)
424 if len(portals_by_host.keys()) < minimum_gateways:
425 if minimum_gateways == 1:
426 msg = 'At least one portal is required'
427 else:
428 msg = 'At least {} portals are required'.format(minimum_gateways)
429 raise DashboardException(msg=msg,
430 code='portals_required',
431 component='iscsi')
432
433 for portal in portals:
434 gateway_name = portal['host']
435 try:
436 IscsiClient.instance(gateway_name=gateway_name).ping()
437 except RequestException:
438 raise DashboardException(msg='iSCSI REST Api not available for gateway '
439 '{}'.format(gateway_name),
440 code='ceph_iscsi_rest_api_not_available_for_gateway',
441 component='iscsi')
442
443 for disk in disks:
444 pool = disk['pool']
445 image = disk['image']
446 backstore = disk['backstore']
447 required_rbd_features = settings['required_rbd_features'][backstore]
81eedcae 448 unsupported_rbd_features = settings['unsupported_rbd_features'][backstore]
11fdf7f2 449 IscsiTarget._validate_image(pool, image, backstore, required_rbd_features,
81eedcae
TL
450 unsupported_rbd_features)
451
452 initiators = []
453 for group in groups:
454 initiators = initiators + group['members']
455 if len(initiators) != len(set(initiators)):
456 raise DashboardException(msg='Each initiator can only be part of 1 group at a time',
457 code='initiator_in_multiple_groups',
458 component='iscsi')
11fdf7f2
TL
459
460 @staticmethod
81eedcae 461 def _validate_image(pool, image, backstore, required_rbd_features, unsupported_rbd_features):
11fdf7f2
TL
462 try:
463 ioctx = mgr.rados.open_ioctx(pool)
464 try:
465 with rbd.Image(ioctx, image) as img:
466 if img.features() & required_rbd_features != required_rbd_features:
467 raise DashboardException(msg='Image {} cannot be exported using {} '
468 'backstore because required features are '
469 'missing (required features are '
470 '{})'.format(image,
471 backstore,
472 format_bitmask(
473 required_rbd_features)),
474 code='image_missing_required_features',
475 component='iscsi')
81eedcae 476 if img.features() & unsupported_rbd_features != 0:
11fdf7f2
TL
477 raise DashboardException(msg='Image {} cannot be exported using {} '
478 'backstore because it contains unsupported '
81eedcae 479 'features ('
11fdf7f2
TL
480 '{})'.format(image,
481 backstore,
482 format_bitmask(
81eedcae 483 unsupported_rbd_features)),
11fdf7f2
TL
484 code='image_contains_unsupported_features',
485 component='iscsi')
486
487 except rbd.ImageNotFound:
488 raise DashboardException(msg='Image {} does not exist'.format(image),
489 code='image_does_not_exist',
490 component='iscsi')
491 except rados.ObjectNotFound:
492 raise DashboardException(msg='Pool {} does not exist'.format(pool),
493 code='pool_does_not_exist',
494 component='iscsi')
495
496 @staticmethod
497 def _create(target_iqn, target_controls, acl_enabled,
498 portals, disks, clients, groups,
499 task_progress_begin, task_progress_end, config):
500 target_config = config['targets'].get(target_iqn, None)
501 TaskManager.current_task().set_progress(task_progress_begin)
502 portals_by_host = IscsiTarget._get_portals_by_host(portals)
503 n_hosts = len(portals_by_host)
504 n_disks = len(disks)
505 n_clients = len(clients)
506 n_groups = len(groups)
507 task_progress_steps = n_hosts + n_disks + n_clients + n_groups
508 task_progress_inc = 0
509 if task_progress_steps != 0:
510 task_progress_inc = int((task_progress_end - task_progress_begin) / task_progress_steps)
511 try:
512 gateway_name = portals[0]['host']
513 if not target_config:
514 IscsiClient.instance(gateway_name=gateway_name).create_target(target_iqn,
515 target_controls)
494da23a
TL
516 for host, ip_list in portals_by_host.items():
517 if not target_config or host not in target_config['portals']:
11fdf7f2
TL
518 IscsiClient.instance(gateway_name=gateway_name).create_gateway(target_iqn,
519 host,
520 ip_list)
494da23a 521 TaskManager.current_task().inc_progress(task_progress_inc)
11fdf7f2
TL
522 targetauth_action = ('enable_acl' if acl_enabled else 'disable_acl')
523 IscsiClient.instance(gateway_name=gateway_name).update_targetauth(target_iqn,
524 targetauth_action)
525 for disk in disks:
526 pool = disk['pool']
527 image = disk['image']
528 image_id = '{}/{}'.format(pool, image)
529 if image_id not in config['disks']:
530 backstore = disk['backstore']
531 IscsiClient.instance(gateway_name=gateway_name).create_disk(pool,
532 image,
533 backstore)
534 if not target_config or image_id not in target_config['disks']:
535 IscsiClient.instance(gateway_name=gateway_name).create_target_lun(target_iqn,
536 image_id)
537 controls = disk['controls']
538 if controls:
539 IscsiClient.instance(gateway_name=gateway_name).reconfigure_disk(pool,
540 image,
541 controls)
542 TaskManager.current_task().inc_progress(task_progress_inc)
543 for client in clients:
544 client_iqn = client['client_iqn']
545 if not target_config or client_iqn not in target_config['clients']:
546 IscsiClient.instance(gateway_name=gateway_name).create_client(target_iqn,
547 client_iqn)
548 for lun in client['luns']:
549 pool = lun['pool']
550 image = lun['image']
551 image_id = '{}/{}'.format(pool, image)
552 IscsiClient.instance(gateway_name=gateway_name).create_client_lun(
553 target_iqn, client_iqn, image_id)
554 user = client['auth']['user']
555 password = client['auth']['password']
556 m_user = client['auth']['mutual_user']
557 m_password = client['auth']['mutual_password']
558 IscsiClient.instance(gateway_name=gateway_name).create_client_auth(
559 target_iqn, client_iqn, user, password, m_user, m_password)
560 TaskManager.current_task().inc_progress(task_progress_inc)
561 for group in groups:
562 group_id = group['group_id']
563 members = group['members']
564 image_ids = []
565 for disk in group['disks']:
566 image_ids.append('{}/{}'.format(disk['pool'], disk['image']))
567 if not target_config or group_id not in target_config['groups']:
568 IscsiClient.instance(gateway_name=gateway_name).create_group(
569 target_iqn, group_id, members, image_ids)
570 TaskManager.current_task().inc_progress(task_progress_inc)
571 if target_controls:
572 if not target_config or target_controls != target_config['controls']:
573 IscsiClient.instance(gateway_name=gateway_name).reconfigure_target(
574 target_iqn, target_controls)
575 TaskManager.current_task().set_progress(task_progress_end)
576 except RequestException as e:
577 if e.content:
578 content = json.loads(e.content)
579 content_message = content.get('message')
580 if content_message:
581 raise DashboardException(msg=content_message, component='iscsi')
582 raise DashboardException(e=e, component='iscsi')
583
584 @staticmethod
585 def _config_to_target(target_iqn, config):
586 target_config = config['targets'][target_iqn]
587 portals = []
494da23a
TL
588 for host, portal_config in target_config['portals'].items():
589 for portal_ip in portal_config['portal_ip_addresses']:
11fdf7f2
TL
590 portal = {
591 'host': host,
592 'ip': portal_ip
593 }
594 portals.append(portal)
595 portals = IscsiTarget._sorted_portals(portals)
596 disks = []
597 for target_disk in target_config['disks']:
598 disk_config = config['disks'][target_disk]
599 disk = {
600 'pool': disk_config['pool'],
601 'image': disk_config['image'],
602 'controls': disk_config['controls'],
603 'backstore': disk_config['backstore']
604 }
605 disks.append(disk)
606 disks = IscsiTarget._sorted_disks(disks)
607 clients = []
608 for client_iqn, client_config in target_config['clients'].items():
609 luns = []
610 for client_lun in client_config['luns'].keys():
611 pool, image = client_lun.split('/', 1)
612 lun = {
613 'pool': pool,
614 'image': image
615 }
616 luns.append(lun)
617 user = client_config['auth']['username']
618 password = client_config['auth']['password']
619 mutual_user = client_config['auth']['mutual_username']
620 mutual_password = client_config['auth']['mutual_password']
621 client = {
622 'client_iqn': client_iqn,
623 'luns': luns,
624 'auth': {
625 'user': user,
626 'password': password,
627 'mutual_user': mutual_user,
628 'mutual_password': mutual_password
629 }
630 }
631 clients.append(client)
632 clients = IscsiTarget._sorted_clients(clients)
633 groups = []
634 for group_id, group_config in target_config['groups'].items():
635 group_disks = []
636 for group_disk_key, _ in group_config['disks'].items():
637 pool, image = group_disk_key.split('/', 1)
638 group_disk = {
639 'pool': pool,
640 'image': image
641 }
642 group_disks.append(group_disk)
643 group = {
644 'group_id': group_id,
645 'disks': group_disks,
646 'members': group_config['members'],
647 }
648 groups.append(group)
649 groups = IscsiTarget._sorted_groups(groups)
650 target_controls = target_config['controls']
651 for key, value in target_controls.items():
652 if isinstance(value, bool):
653 target_controls[key] = 'Yes' if value else 'No'
654 acl_enabled = target_config['acl_enabled']
655 target = {
656 'target_iqn': target_iqn,
657 'portals': portals,
658 'disks': disks,
659 'clients': clients,
660 'groups': groups,
661 'target_controls': target_controls,
662 'acl_enabled': acl_enabled
663 }
664 return target
665
666 @staticmethod
667 def _set_info(target):
668 if not target['portals']:
669 return
670 target_iqn = target['target_iqn']
671 gateway_name = target['portals'][0]['host']
672 target_info = IscsiClient.instance(gateway_name=gateway_name).get_targetinfo(target_iqn)
673 target['info'] = target_info
494da23a
TL
674 for client in target['clients']:
675 client_iqn = client['client_iqn']
676 client_info = IscsiClient.instance(gateway_name=gateway_name).get_clientinfo(
677 target_iqn, client_iqn)
678 client['info'] = client_info
11fdf7f2
TL
679
680 @staticmethod
681 def _sorted_portals(portals):
682 portals = portals or []
683 return sorted(portals, key=lambda p: '{}.{}'.format(p['host'], p['ip']))
684
685 @staticmethod
686 def _sorted_disks(disks):
687 disks = disks or []
688 return sorted(disks, key=lambda d: '{}.{}'.format(d['pool'], d['image']))
689
690 @staticmethod
691 def _sorted_clients(clients):
692 clients = clients or []
693 for client in clients:
694 client['luns'] = sorted(client['luns'],
695 key=lambda d: '{}.{}'.format(d['pool'], d['image']))
696 return sorted(clients, key=lambda c: c['client_iqn'])
697
698 @staticmethod
699 def _sorted_groups(groups):
700 groups = groups or []
701 for group in groups:
702 group['disks'] = sorted(group['disks'],
703 key=lambda d: '{}.{}'.format(d['pool'], d['image']))
704 group['members'] = sorted(group['members'])
705 return sorted(groups, key=lambda g: g['group_id'])
706
707 @staticmethod
708 def _get_portals_by_host(portals):
709 portals_by_host = {}
710 for portal in portals:
711 host = portal['host']
712 ip = portal['ip']
713 if host not in portals_by_host:
714 portals_by_host[host] = []
715 portals_by_host[host].append(ip)
716 return portals_by_host