]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/controllers/iscsi.py
build: use dgit for download target
[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
81eedcae 28 REQUIRED_CEPH_ISCSI_CONFIG_VERSION = 9
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,
282 new_portals, new_groups, group_id, new_clients,
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,
290 new_portals, new_clients, client_iqn,
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,
297 new_target_controls, new_portals,
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)
304 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
305 new_portals):
306 IscsiClient.instance(gateway_name=gateway_name).delete_target(target_iqn)
307 TaskManager.current_task().set_progress(task_progress_end)
308 return IscsiClient.instance(gateway_name=gateway_name).get_config()
309
310 @staticmethod
311 def _get_group(groups, group_id):
312 for group in groups:
313 if group['group_id'] == group_id:
314 return group
315 return None
316
317 @staticmethod
318 def _group_deletion_required(target, new_target_iqn, new_target_controls, new_portals,
319 new_groups, group_id, new_clients, new_disks):
320 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
321 new_portals):
322 return True
323 new_group = IscsiTarget._get_group(new_groups, group_id)
324 if not new_group:
325 return True
326 old_group = IscsiTarget._get_group(target['groups'], group_id)
327 if new_group != old_group:
328 return True
329 # Check if any client inside this group has changed
330 for client_iqn in new_group['members']:
331 if IscsiTarget._client_deletion_required(target, new_target_iqn, new_target_controls,
332 new_portals, new_clients, client_iqn,
333 new_groups, []):
334 return True
335 # Check if any disk inside this group has changed
336 for disk in new_group['disks']:
337 image_id = '{}/{}'.format(disk['pool'], disk['image'])
338 if IscsiTarget._target_lun_deletion_required(target, new_target_iqn,
339 new_target_controls, new_portals,
340 new_disks, image_id):
341 return True
342 return False
343
344 @staticmethod
345 def _get_client(clients, client_iqn):
346 for client in clients:
347 if client['client_iqn'] == client_iqn:
348 return client
349 return None
350
351 @staticmethod
352 def _client_deletion_required(target, new_target_iqn, new_target_controls, new_portals,
353 new_clients, client_iqn, new_groups, deleted_groups):
354 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
355 new_portals):
356 return True
357 new_client = deepcopy(IscsiTarget._get_client(new_clients, client_iqn))
358 if not new_client:
359 return True
360 # Disks inherited from groups must be considered
361 for group in new_groups:
362 if client_iqn in group['members']:
363 new_client['luns'] += group['disks']
364 old_client = IscsiTarget._get_client(target['clients'], client_iqn)
365 if new_client != old_client:
366 return True
367 # Check if client belongs to a groups that has been deleted
368 for group in target['groups']:
369 if group['group_id'] in deleted_groups and client_iqn in group['members']:
370 return True
371 return False
372
373 @staticmethod
374 def _get_disk(disks, image_id):
375 for disk in disks:
376 if '{}/{}'.format(disk['pool'], disk['image']) == image_id:
377 return disk
378 return None
379
380 @staticmethod
381 def _target_lun_deletion_required(target, new_target_iqn, new_target_controls, new_portals,
382 new_disks, image_id):
383 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
384 new_portals):
385 return True
386 new_disk = IscsiTarget._get_disk(new_disks, image_id)
387 if not new_disk:
388 return True
389 old_disk = IscsiTarget._get_disk(target['disks'], image_id)
390 if new_disk != old_disk:
391 return True
392 return False
393
394 @staticmethod
395 def _target_deletion_required(target, new_target_iqn, new_target_controls, new_portals):
396 if target['target_iqn'] != new_target_iqn:
397 return True
398 if target['target_controls'] != new_target_controls:
399 return True
400 if target['portals'] != new_portals:
401 return True
402 return False
403
404 @staticmethod
81eedcae 405 def _validate(target_iqn, portals, disks, groups):
11fdf7f2
TL
406 if not target_iqn:
407 raise DashboardException(msg='Target IQN is required',
408 code='target_iqn_required',
409 component='iscsi')
410
411 settings = IscsiClient.instance().get_settings()
412 minimum_gateways = max(1, settings['config']['minimum_gateways'])
413 portals_by_host = IscsiTarget._get_portals_by_host(portals)
414 if len(portals_by_host.keys()) < minimum_gateways:
415 if minimum_gateways == 1:
416 msg = 'At least one portal is required'
417 else:
418 msg = 'At least {} portals are required'.format(minimum_gateways)
419 raise DashboardException(msg=msg,
420 code='portals_required',
421 component='iscsi')
422
423 for portal in portals:
424 gateway_name = portal['host']
425 try:
426 IscsiClient.instance(gateway_name=gateway_name).ping()
427 except RequestException:
428 raise DashboardException(msg='iSCSI REST Api not available for gateway '
429 '{}'.format(gateway_name),
430 code='ceph_iscsi_rest_api_not_available_for_gateway',
431 component='iscsi')
432
433 for disk in disks:
434 pool = disk['pool']
435 image = disk['image']
436 backstore = disk['backstore']
437 required_rbd_features = settings['required_rbd_features'][backstore]
81eedcae 438 unsupported_rbd_features = settings['unsupported_rbd_features'][backstore]
11fdf7f2 439 IscsiTarget._validate_image(pool, image, backstore, required_rbd_features,
81eedcae
TL
440 unsupported_rbd_features)
441
442 initiators = []
443 for group in groups:
444 initiators = initiators + group['members']
445 if len(initiators) != len(set(initiators)):
446 raise DashboardException(msg='Each initiator can only be part of 1 group at a time',
447 code='initiator_in_multiple_groups',
448 component='iscsi')
11fdf7f2
TL
449
450 @staticmethod
81eedcae 451 def _validate_image(pool, image, backstore, required_rbd_features, unsupported_rbd_features):
11fdf7f2
TL
452 try:
453 ioctx = mgr.rados.open_ioctx(pool)
454 try:
455 with rbd.Image(ioctx, image) as img:
456 if img.features() & required_rbd_features != required_rbd_features:
457 raise DashboardException(msg='Image {} cannot be exported using {} '
458 'backstore because required features are '
459 'missing (required features are '
460 '{})'.format(image,
461 backstore,
462 format_bitmask(
463 required_rbd_features)),
464 code='image_missing_required_features',
465 component='iscsi')
81eedcae 466 if img.features() & unsupported_rbd_features != 0:
11fdf7f2
TL
467 raise DashboardException(msg='Image {} cannot be exported using {} '
468 'backstore because it contains unsupported '
81eedcae 469 'features ('
11fdf7f2
TL
470 '{})'.format(image,
471 backstore,
472 format_bitmask(
81eedcae 473 unsupported_rbd_features)),
11fdf7f2
TL
474 code='image_contains_unsupported_features',
475 component='iscsi')
476
477 except rbd.ImageNotFound:
478 raise DashboardException(msg='Image {} does not exist'.format(image),
479 code='image_does_not_exist',
480 component='iscsi')
481 except rados.ObjectNotFound:
482 raise DashboardException(msg='Pool {} does not exist'.format(pool),
483 code='pool_does_not_exist',
484 component='iscsi')
485
486 @staticmethod
487 def _create(target_iqn, target_controls, acl_enabled,
488 portals, disks, clients, groups,
489 task_progress_begin, task_progress_end, config):
490 target_config = config['targets'].get(target_iqn, None)
491 TaskManager.current_task().set_progress(task_progress_begin)
492 portals_by_host = IscsiTarget._get_portals_by_host(portals)
493 n_hosts = len(portals_by_host)
494 n_disks = len(disks)
495 n_clients = len(clients)
496 n_groups = len(groups)
497 task_progress_steps = n_hosts + n_disks + n_clients + n_groups
498 task_progress_inc = 0
499 if task_progress_steps != 0:
500 task_progress_inc = int((task_progress_end - task_progress_begin) / task_progress_steps)
501 try:
502 gateway_name = portals[0]['host']
503 if not target_config:
504 IscsiClient.instance(gateway_name=gateway_name).create_target(target_iqn,
505 target_controls)
506 for host, ip_list in portals_by_host.items():
507 IscsiClient.instance(gateway_name=gateway_name).create_gateway(target_iqn,
508 host,
509 ip_list)
510 TaskManager.current_task().inc_progress(task_progress_inc)
511 targetauth_action = ('enable_acl' if acl_enabled else 'disable_acl')
512 IscsiClient.instance(gateway_name=gateway_name).update_targetauth(target_iqn,
513 targetauth_action)
514 for disk in disks:
515 pool = disk['pool']
516 image = disk['image']
517 image_id = '{}/{}'.format(pool, image)
518 if image_id not in config['disks']:
519 backstore = disk['backstore']
520 IscsiClient.instance(gateway_name=gateway_name).create_disk(pool,
521 image,
522 backstore)
523 if not target_config or image_id not in target_config['disks']:
524 IscsiClient.instance(gateway_name=gateway_name).create_target_lun(target_iqn,
525 image_id)
526 controls = disk['controls']
527 if controls:
528 IscsiClient.instance(gateway_name=gateway_name).reconfigure_disk(pool,
529 image,
530 controls)
531 TaskManager.current_task().inc_progress(task_progress_inc)
532 for client in clients:
533 client_iqn = client['client_iqn']
534 if not target_config or client_iqn not in target_config['clients']:
535 IscsiClient.instance(gateway_name=gateway_name).create_client(target_iqn,
536 client_iqn)
537 for lun in client['luns']:
538 pool = lun['pool']
539 image = lun['image']
540 image_id = '{}/{}'.format(pool, image)
541 IscsiClient.instance(gateway_name=gateway_name).create_client_lun(
542 target_iqn, client_iqn, image_id)
543 user = client['auth']['user']
544 password = client['auth']['password']
545 m_user = client['auth']['mutual_user']
546 m_password = client['auth']['mutual_password']
547 IscsiClient.instance(gateway_name=gateway_name).create_client_auth(
548 target_iqn, client_iqn, user, password, m_user, m_password)
549 TaskManager.current_task().inc_progress(task_progress_inc)
550 for group in groups:
551 group_id = group['group_id']
552 members = group['members']
553 image_ids = []
554 for disk in group['disks']:
555 image_ids.append('{}/{}'.format(disk['pool'], disk['image']))
556 if not target_config or group_id not in target_config['groups']:
557 IscsiClient.instance(gateway_name=gateway_name).create_group(
558 target_iqn, group_id, members, image_ids)
559 TaskManager.current_task().inc_progress(task_progress_inc)
560 if target_controls:
561 if not target_config or target_controls != target_config['controls']:
562 IscsiClient.instance(gateway_name=gateway_name).reconfigure_target(
563 target_iqn, target_controls)
564 TaskManager.current_task().set_progress(task_progress_end)
565 except RequestException as e:
566 if e.content:
567 content = json.loads(e.content)
568 content_message = content.get('message')
569 if content_message:
570 raise DashboardException(msg=content_message, component='iscsi')
571 raise DashboardException(e=e, component='iscsi')
572
573 @staticmethod
574 def _config_to_target(target_iqn, config):
575 target_config = config['targets'][target_iqn]
576 portals = []
577 for host in target_config['portals'].keys():
578 ips = IscsiClient.instance(gateway_name=host).get_ip_addresses()['data']
579 portal_ips = [ip for ip in ips if ip in target_config['ip_list']]
580 for portal_ip in portal_ips:
581 portal = {
582 'host': host,
583 'ip': portal_ip
584 }
585 portals.append(portal)
586 portals = IscsiTarget._sorted_portals(portals)
587 disks = []
588 for target_disk in target_config['disks']:
589 disk_config = config['disks'][target_disk]
590 disk = {
591 'pool': disk_config['pool'],
592 'image': disk_config['image'],
593 'controls': disk_config['controls'],
594 'backstore': disk_config['backstore']
595 }
596 disks.append(disk)
597 disks = IscsiTarget._sorted_disks(disks)
598 clients = []
599 for client_iqn, client_config in target_config['clients'].items():
600 luns = []
601 for client_lun in client_config['luns'].keys():
602 pool, image = client_lun.split('/', 1)
603 lun = {
604 'pool': pool,
605 'image': image
606 }
607 luns.append(lun)
608 user = client_config['auth']['username']
609 password = client_config['auth']['password']
610 mutual_user = client_config['auth']['mutual_username']
611 mutual_password = client_config['auth']['mutual_password']
612 client = {
613 'client_iqn': client_iqn,
614 'luns': luns,
615 'auth': {
616 'user': user,
617 'password': password,
618 'mutual_user': mutual_user,
619 'mutual_password': mutual_password
620 }
621 }
622 clients.append(client)
623 clients = IscsiTarget._sorted_clients(clients)
624 groups = []
625 for group_id, group_config in target_config['groups'].items():
626 group_disks = []
627 for group_disk_key, _ in group_config['disks'].items():
628 pool, image = group_disk_key.split('/', 1)
629 group_disk = {
630 'pool': pool,
631 'image': image
632 }
633 group_disks.append(group_disk)
634 group = {
635 'group_id': group_id,
636 'disks': group_disks,
637 'members': group_config['members'],
638 }
639 groups.append(group)
640 groups = IscsiTarget._sorted_groups(groups)
641 target_controls = target_config['controls']
642 for key, value in target_controls.items():
643 if isinstance(value, bool):
644 target_controls[key] = 'Yes' if value else 'No'
645 acl_enabled = target_config['acl_enabled']
646 target = {
647 'target_iqn': target_iqn,
648 'portals': portals,
649 'disks': disks,
650 'clients': clients,
651 'groups': groups,
652 'target_controls': target_controls,
653 'acl_enabled': acl_enabled
654 }
655 return target
656
657 @staticmethod
658 def _set_info(target):
659 if not target['portals']:
660 return
661 target_iqn = target['target_iqn']
662 gateway_name = target['portals'][0]['host']
663 target_info = IscsiClient.instance(gateway_name=gateway_name).get_targetinfo(target_iqn)
664 target['info'] = target_info
665
666 @staticmethod
667 def _sorted_portals(portals):
668 portals = portals or []
669 return sorted(portals, key=lambda p: '{}.{}'.format(p['host'], p['ip']))
670
671 @staticmethod
672 def _sorted_disks(disks):
673 disks = disks or []
674 return sorted(disks, key=lambda d: '{}.{}'.format(d['pool'], d['image']))
675
676 @staticmethod
677 def _sorted_clients(clients):
678 clients = clients or []
679 for client in clients:
680 client['luns'] = sorted(client['luns'],
681 key=lambda d: '{}.{}'.format(d['pool'], d['image']))
682 return sorted(clients, key=lambda c: c['client_iqn'])
683
684 @staticmethod
685 def _sorted_groups(groups):
686 groups = groups or []
687 for group in groups:
688 group['disks'] = sorted(group['disks'],
689 key=lambda d: '{}.{}'.format(d['pool'], d['image']))
690 group['members'] = sorted(group['members'])
691 return sorted(groups, key=lambda g: g['group_id'])
692
693 @staticmethod
694 def _get_portals_by_host(portals):
695 portals_by_host = {}
696 for portal in portals:
697 host = portal['host']
698 ip = portal['ip']
699 if host not in portals_by_host:
700 portals_by_host[host] = []
701 portals_by_host[host].append(ip)
702 return portals_by_host