]>
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 | ||
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) | |
148 | class 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 | ||
175 | def 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) | |
180 | class 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 |