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