]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | # -*- coding: utf-8 -*- |
2 | # pylint: disable=too-many-branches | |
3 | from __future__ import absolute_import | |
4 | ||
5 | from copy import deepcopy | |
6 | import json | |
7 | import cherrypy | |
8 | ||
9 | import rados | |
10 | import rbd | |
11 | ||
12 | from . import ApiController, UiApiController, RESTController, BaseController, Endpoint,\ | |
13 | ReadPermission, UpdatePermission, Task | |
14 | from .. import mgr | |
15 | from ..rest_client import RequestException | |
16 | from ..security import Scope | |
17 | from ..services.iscsi_client import IscsiClient | |
18 | from ..services.iscsi_cli import IscsiGatewaysConfig | |
19 | from ..services.rbd import format_bitmask | |
20 | from ..services.tcmu_service import TcmuService | |
21 | from ..exceptions import DashboardException | |
22 | from ..tools import TaskManager | |
23 | ||
24 | ||
25 | @UiApiController('/iscsi', Scope.ISCSI) | |
26 | class 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) | |
144 | class 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 | ||
171 | def 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) | |
176 | class 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 |