]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/controllers/iscsi.py
import quincy beta 17.1.0
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / iscsi.py
CommitLineData
11fdf7f2 1# -*- coding: utf-8 -*-
f67539c2 2# pylint: disable=C0302
11fdf7f2 3# pylint: disable=too-many-branches
f6b5b4d7 4# pylint: disable=too-many-lines
11fdf7f2 5
11fdf7f2 6import json
f67539c2
TL
7import re
8from copy import deepcopy
20effc67 9from typing import Any, Dict, List, no_type_check
11fdf7f2 10
f67539c2 11import cherrypy
11fdf7f2
TL
12import rados
13import rbd
14
11fdf7f2 15from .. import mgr
f67539c2 16from ..exceptions import DashboardException
11fdf7f2
TL
17from ..rest_client import RequestException
18from ..security import Scope
20effc67 19from ..services.exception import handle_request_error
11fdf7f2 20from ..services.iscsi_cli import IscsiGatewaysConfig
f67539c2 21from ..services.iscsi_client import IscsiClient
92f5a8d4 22from ..services.iscsi_config import IscsiGatewayDoesNotExist
11fdf7f2
TL
23from ..services.rbd import format_bitmask
24from ..services.tcmu_service import TcmuService
f67539c2 25from ..tools import TaskManager, str_to_bool
a4b75251
TL
26from . import APIDoc, APIRouter, BaseController, Endpoint, EndpointDoc, \
27 ReadPermission, RESTController, Task, UIRouter, UpdatePermission
11fdf7f2 28
f67539c2
TL
29ISCSI_SCHEMA = {
30 'user': (str, 'username'),
31 'password': (str, 'password'),
32 'mutual_user': (str, ''),
33 'mutual_password': (str, '')
34}
35
11fdf7f2 36
a4b75251 37@UIRouter('/iscsi', Scope.ISCSI)
11fdf7f2
TL
38class IscsiUi(BaseController):
39
eafe8130
TL
40 REQUIRED_CEPH_ISCSI_CONFIG_MIN_VERSION = 10
41 REQUIRED_CEPH_ISCSI_CONFIG_MAX_VERSION = 11
11fdf7f2
TL
42
43 @Endpoint()
44 @ReadPermission
9f95a23c 45 @no_type_check
11fdf7f2
TL
46 def status(self):
47 status = {'available': False}
92f5a8d4
TL
48 try:
49 gateway = get_available_gateway()
50 except DashboardException as e:
51 status['message'] = str(e)
11fdf7f2
TL
52 return status
53 try:
92f5a8d4 54 config = IscsiClient.instance(gateway_name=gateway).get_config()
eafe8130
TL
55 if config['version'] < IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_MIN_VERSION or \
56 config['version'] > IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_MAX_VERSION:
57 status['message'] = 'Unsupported `ceph-iscsi` config version. ' \
58 'Expected >= {} and <= {} but found' \
59 ' {}.'.format(IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_MIN_VERSION,
60 IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_MAX_VERSION,
61 config['version'])
11fdf7f2
TL
62 return status
63 status['available'] = True
64 except RequestException as e:
65 if e.content:
81eedcae
TL
66 try:
67 content = json.loads(e.content)
68 content_message = content.get('message')
69 except ValueError:
70 content_message = e.content
11fdf7f2
TL
71 if content_message:
72 status['message'] = content_message
81eedcae 73
11fdf7f2
TL
74 return status
75
eafe8130
TL
76 @Endpoint()
77 @ReadPermission
78 def version(self):
92f5a8d4
TL
79 gateway = get_available_gateway()
80 config = IscsiClient.instance(gateway_name=gateway).get_config()
eafe8130 81 return {
92f5a8d4 82 'ceph_iscsi_config_version': config['version']
eafe8130
TL
83 }
84
11fdf7f2
TL
85 @Endpoint()
86 @ReadPermission
87 def settings(self):
92f5a8d4
TL
88 gateway = get_available_gateway()
89 settings = IscsiClient.instance(gateway_name=gateway).get_settings()
eafe8130
TL
90 if 'target_controls_limits' in settings:
91 target_default_controls = settings['target_default_controls']
92 for ctrl_k, ctrl_v in target_default_controls.items():
93 limits = settings['target_controls_limits'].get(ctrl_k, {})
94 if 'type' not in limits:
95 # default
96 limits['type'] = 'int'
97 # backward compatibility
98 if target_default_controls[ctrl_k] in ['Yes', 'No']:
99 limits['type'] = 'bool'
100 target_default_controls[ctrl_k] = str_to_bool(ctrl_v)
101 settings['target_controls_limits'][ctrl_k] = limits
102 if 'disk_controls_limits' in settings:
103 for backstore, disk_controls_limits in settings['disk_controls_limits'].items():
104 disk_default_controls = settings['disk_default_controls'][backstore]
105 for ctrl_k, ctrl_v in disk_default_controls.items():
106 limits = disk_controls_limits.get(ctrl_k, {})
107 if 'type' not in limits:
108 # default
109 limits['type'] = 'int'
110 settings['disk_controls_limits'][backstore][ctrl_k] = limits
111 return settings
11fdf7f2
TL
112
113 @Endpoint()
114 @ReadPermission
115 def portals(self):
116 portals = []
117 gateways_config = IscsiGatewaysConfig.get_gateways_config()
81eedcae 118 for name in gateways_config['gateways']:
92f5a8d4
TL
119 try:
120 ip_addresses = IscsiClient.instance(gateway_name=name).get_ip_addresses()
121 portals.append({'name': name, 'ip_addresses': ip_addresses['data']})
122 except RequestException:
123 pass
11fdf7f2
TL
124 return sorted(portals, key=lambda p: '{}.{}'.format(p['name'], p['ip_addresses']))
125
126 @Endpoint()
127 @ReadPermission
128 def overview(self):
11fdf7f2
TL
129 gateways_names = IscsiGatewaysConfig.get_gateways_config()['gateways'].keys()
130 config = None
131 for gateway_name in gateways_names:
132 try:
133 config = IscsiClient.instance(gateway_name=gateway_name).get_config()
134 break
135 except RequestException:
136 pass
137
20effc67
TL
138 result_gateways = self._get_gateways_info(gateways_names, config)
139 result_images = self._get_images_info(config)
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 }
11fdf7f2 145
20effc67 146 def _get_images_info(self, config):
11fdf7f2 147 # Images info
20effc67 148 result_images = []
11fdf7f2
TL
149 if config:
150 tcmu_info = TcmuService.get_iscsi_info()
151 for _, disk_config in config['disks'].items():
152 image = {
153 'pool': disk_config['pool'],
154 'image': disk_config['image'],
155 'backstore': disk_config['backstore'],
156 'optimized_since': None,
157 'stats': None,
158 'stats_history': None
159 }
160 tcmu_image_info = TcmuService.get_image_info(image['pool'],
161 image['image'],
162 tcmu_info)
163 if tcmu_image_info:
164 if 'optimized_since' in tcmu_image_info:
165 image['optimized_since'] = tcmu_image_info['optimized_since']
166 if 'stats' in tcmu_image_info:
167 image['stats'] = tcmu_image_info['stats']
168 if 'stats_history' in tcmu_image_info:
169 image['stats_history'] = tcmu_image_info['stats_history']
170 result_images.append(image)
20effc67 171 return result_images
11fdf7f2 172
20effc67
TL
173 def _get_gateways_info(self, gateways_names, config):
174 result_gateways = []
175 # Gateways info
176 for gateway_name in gateways_names:
177 gateway = {
178 'name': gateway_name,
179 'state': '',
180 'num_targets': 'n/a',
181 'num_sessions': 'n/a'
182 }
183 try:
184 IscsiClient.instance(gateway_name=gateway_name).ping()
185 gateway['state'] = 'up'
186 if config:
187 gateway['num_sessions'] = 0
188 if gateway_name in config['gateways']:
189 gatewayinfo = IscsiClient.instance(
190 gateway_name=gateway_name).get_gatewayinfo()
191 gateway['num_sessions'] = gatewayinfo['num_sessions']
192 except RequestException:
193 gateway['state'] = 'down'
194 if config:
195 gateway['num_targets'] = len([target for _, target in config['targets'].items()
196 if gateway_name in target['portals']])
197 result_gateways.append(gateway)
198 return result_gateways
11fdf7f2
TL
199
200
a4b75251
TL
201@APIRouter('/iscsi', Scope.ISCSI)
202@APIDoc("Iscsi Management API", "Iscsi")
11fdf7f2 203class Iscsi(BaseController):
11fdf7f2 204 @Endpoint('GET', 'discoveryauth')
81eedcae 205 @ReadPermission
f67539c2
TL
206 @EndpointDoc("Get Iscsi discoveryauth Details",
207 responses={'200': [ISCSI_SCHEMA]})
11fdf7f2 208 def get_discoveryauth(self):
92f5a8d4
TL
209 gateway = get_available_gateway()
210 return self._get_discoveryauth(gateway)
11fdf7f2 211
f67539c2
TL
212 @Endpoint('PUT', 'discoveryauth',
213 query_params=['user', 'password', 'mutual_user', 'mutual_password'])
11fdf7f2 214 @UpdatePermission
f67539c2
TL
215 @EndpointDoc("Set Iscsi discoveryauth",
216 parameters={
217 'user': (str, 'Username'),
218 'password': (str, 'Password'),
219 'mutual_user': (str, 'Mutual UserName'),
220 'mutual_password': (str, 'Mutual Password'),
221 })
11fdf7f2 222 def set_discoveryauth(self, user, password, mutual_user, mutual_password):
1911f103
TL
223 validate_auth({
224 'user': user,
225 'password': password,
226 'mutual_user': mutual_user,
227 'mutual_password': mutual_password
228 })
229
92f5a8d4
TL
230 gateway = get_available_gateway()
231 config = IscsiClient.instance(gateway_name=gateway).get_config()
232 gateway_names = list(config['gateways'].keys())
233 validate_rest_api(gateway_names)
234 IscsiClient.instance(gateway_name=gateway).update_discoveryauth(user,
235 password,
236 mutual_user,
237 mutual_password)
238 return self._get_discoveryauth(gateway)
239
240 def _get_discoveryauth(self, gateway):
241 config = IscsiClient.instance(gateway_name=gateway).get_config()
11fdf7f2
TL
242 user = config['discovery_auth']['username']
243 password = config['discovery_auth']['password']
244 mutual_user = config['discovery_auth']['mutual_username']
245 mutual_password = config['discovery_auth']['mutual_password']
246 return {
247 'user': user,
248 'password': password,
249 'mutual_user': mutual_user,
250 'mutual_password': mutual_password
251 }
252
253
254def iscsi_target_task(name, metadata, wait_for=2.0):
255 return Task("iscsi/target/{}".format(name), metadata, wait_for)
256
257
a4b75251
TL
258@APIRouter('/iscsi/target', Scope.ISCSI)
259@APIDoc("Get Iscsi Target Details", "IscsiTarget")
11fdf7f2
TL
260class IscsiTarget(RESTController):
261
262 def list(self):
92f5a8d4
TL
263 gateway = get_available_gateway()
264 config = IscsiClient.instance(gateway_name=gateway).get_config()
11fdf7f2
TL
265 targets = []
266 for target_iqn in config['targets'].keys():
267 target = IscsiTarget._config_to_target(target_iqn, config)
268 IscsiTarget._set_info(target)
269 targets.append(target)
270 return targets
271
272 def get(self, target_iqn):
92f5a8d4
TL
273 gateway = get_available_gateway()
274 config = IscsiClient.instance(gateway_name=gateway).get_config()
11fdf7f2
TL
275 if target_iqn not in config['targets']:
276 raise cherrypy.HTTPError(404)
277 target = IscsiTarget._config_to_target(target_iqn, config)
278 IscsiTarget._set_info(target)
279 return target
280
281 @iscsi_target_task('delete', {'target_iqn': '{target_iqn}'})
282 def delete(self, target_iqn):
92f5a8d4
TL
283 gateway = get_available_gateway()
284 config = IscsiClient.instance(gateway_name=gateway).get_config()
11fdf7f2
TL
285 if target_iqn not in config['targets']:
286 raise DashboardException(msg='Target does not exist',
287 code='target_does_not_exist',
288 component='iscsi')
92f5a8d4
TL
289 portal_names = list(config['targets'][target_iqn]['portals'].keys())
290 validate_rest_api(portal_names)
291 if portal_names:
292 portal_name = portal_names[0]
293 target_info = IscsiClient.instance(gateway_name=portal_name).get_targetinfo(target_iqn)
294 if target_info['num_sessions'] > 0:
295 raise DashboardException(msg='Target has active sessions',
296 code='target_has_active_sessions',
297 component='iscsi')
11fdf7f2
TL
298 IscsiTarget._delete(target_iqn, config, 0, 100)
299
300 @iscsi_target_task('create', {'target_iqn': '{target_iqn}'})
301 def create(self, target_iqn=None, target_controls=None, acl_enabled=None,
eafe8130 302 auth=None, portals=None, disks=None, clients=None, groups=None):
11fdf7f2
TL
303 target_controls = target_controls or {}
304 portals = portals or []
305 disks = disks or []
306 clients = clients or []
307 groups = groups or []
308
1911f103
TL
309 validate_auth(auth)
310 for client in clients:
311 validate_auth(client['auth'])
312
92f5a8d4
TL
313 gateway = get_available_gateway()
314 config = IscsiClient.instance(gateway_name=gateway).get_config()
11fdf7f2
TL
315 if target_iqn in config['targets']:
316 raise DashboardException(msg='Target already exists',
317 code='target_already_exists',
318 component='iscsi')
92f5a8d4 319 settings = IscsiClient.instance(gateway_name=gateway).get_settings()
eafe8130
TL
320 IscsiTarget._validate(target_iqn, target_controls, portals, disks, groups, settings)
321
322 IscsiTarget._create(target_iqn, target_controls, acl_enabled, auth, portals, disks,
323 clients, groups, 0, 100, config, settings)
11fdf7f2
TL
324
325 @iscsi_target_task('edit', {'target_iqn': '{target_iqn}'})
326 def set(self, target_iqn, new_target_iqn=None, target_controls=None, acl_enabled=None,
eafe8130 327 auth=None, portals=None, disks=None, clients=None, groups=None):
11fdf7f2
TL
328 target_controls = target_controls or {}
329 portals = IscsiTarget._sorted_portals(portals)
330 disks = IscsiTarget._sorted_disks(disks)
331 clients = IscsiTarget._sorted_clients(clients)
332 groups = IscsiTarget._sorted_groups(groups)
333
1911f103
TL
334 validate_auth(auth)
335 for client in clients:
336 validate_auth(client['auth'])
337
92f5a8d4
TL
338 gateway = get_available_gateway()
339 config = IscsiClient.instance(gateway_name=gateway).get_config()
11fdf7f2
TL
340 if target_iqn not in config['targets']:
341 raise DashboardException(msg='Target does not exist',
342 code='target_does_not_exist',
343 component='iscsi')
344 if target_iqn != new_target_iqn and new_target_iqn in config['targets']:
345 raise DashboardException(msg='Target IQN already in use',
346 code='target_iqn_already_in_use',
347 component='iscsi')
1911f103 348
92f5a8d4
TL
349 settings = IscsiClient.instance(gateway_name=gateway).get_settings()
350 new_portal_names = {p['host'] for p in portals}
351 old_portal_names = set(config['targets'][target_iqn]['portals'].keys())
352 deleted_portal_names = list(old_portal_names - new_portal_names)
353 validate_rest_api(deleted_portal_names)
eafe8130 354 IscsiTarget._validate(new_target_iqn, target_controls, portals, disks, groups, settings)
f6b5b4d7
TL
355 IscsiTarget._validate_delete(gateway, target_iqn, config, new_target_iqn, target_controls,
356 disks, clients, groups)
11fdf7f2
TL
357 config = IscsiTarget._delete(target_iqn, config, 0, 50, new_target_iqn, target_controls,
358 portals, disks, clients, groups)
eafe8130
TL
359 IscsiTarget._create(new_target_iqn, target_controls, acl_enabled, auth, portals, disks,
360 clients, groups, 50, 100, config, settings)
11fdf7f2
TL
361
362 @staticmethod
363 def _delete(target_iqn, config, task_progress_begin, task_progress_end, new_target_iqn=None,
364 new_target_controls=None, new_portals=None, new_disks=None, new_clients=None,
365 new_groups=None):
366 new_target_controls = new_target_controls or {}
367 new_portals = new_portals or []
368 new_disks = new_disks or []
369 new_clients = new_clients or []
370 new_groups = new_groups or []
371
372 TaskManager.current_task().set_progress(task_progress_begin)
373 target_config = config['targets'][target_iqn]
374 if not target_config['portals'].keys():
375 raise DashboardException(msg="Cannot delete a target that doesn't contain any portal",
376 code='cannot_delete_target_without_portals',
377 component='iscsi')
378 target = IscsiTarget._config_to_target(target_iqn, config)
379 n_groups = len(target_config['groups'])
380 n_clients = len(target_config['clients'])
381 n_target_disks = len(target_config['disks'])
382 task_progress_steps = n_groups + n_clients + n_target_disks
383 task_progress_inc = 0
384 if task_progress_steps != 0:
385 task_progress_inc = int((task_progress_end - task_progress_begin) / task_progress_steps)
f91f0fd5 386
20effc67
TL
387 gateway_name = list(target_config['portals'].keys())[0]
388 IscsiTarget._delete_groups(target_config, target, new_target_iqn,
389 new_target_controls, new_groups, gateway_name,
390 target_iqn, task_progress_inc)
391 deleted_clients, deleted_client_luns = IscsiTarget._delete_clients(
392 target_config, target, new_target_iqn, new_target_controls, new_clients,
393 gateway_name, target_iqn, new_groups, task_progress_inc)
394 IscsiTarget._delete_disks(target_config, target, new_target_iqn, new_target_controls,
395 new_disks, deleted_clients, new_groups, deleted_client_luns,
396 gateway_name, target_iqn, task_progress_inc)
397 IscsiTarget._delete_gateways(target, new_portals, gateway_name, target_iqn)
398 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
399 IscsiClient.instance(gateway_name=gateway_name).delete_target(target_iqn)
400 TaskManager.current_task().set_progress(task_progress_end)
401 return IscsiClient.instance(gateway_name=gateway_name).get_config()
f91f0fd5 402
20effc67
TL
403 @staticmethod
404 def _delete_gateways(target, new_portals, gateway_name, target_iqn):
405 old_portals_by_host = IscsiTarget._get_portals_by_host(target['portals'])
406 new_portals_by_host = IscsiTarget._get_portals_by_host(new_portals)
407 for old_portal_host, old_portal_ip_list in old_portals_by_host.items():
408 if IscsiTarget._target_portal_deletion_required(old_portal_host,
409 old_portal_ip_list,
410 new_portals_by_host):
411 IscsiClient.instance(gateway_name=gateway_name).delete_gateway(target_iqn,
412 old_portal_host)
f91f0fd5 413
20effc67
TL
414 @staticmethod
415 def _delete_disks(target_config, target, new_target_iqn, new_target_controls,
416 new_disks, deleted_clients, new_groups, deleted_client_luns,
417 gateway_name, target_iqn, task_progress_inc):
11fdf7f2
TL
418 for image_id in target_config['disks']:
419 if IscsiTarget._target_lun_deletion_required(target, new_target_iqn,
f91f0fd5 420 new_target_controls, new_disks, image_id):
eafe8130 421 all_clients = target_config['clients'].keys()
f91f0fd5
TL
422 not_deleted_clients = [c for c in all_clients if c not in deleted_clients
423 and not IscsiTarget._client_in_group(target['groups'], c)
424 and not IscsiTarget._client_in_group(new_groups, c)]
eafe8130
TL
425 for client_iqn in not_deleted_clients:
426 client_image_ids = target_config['clients'][client_iqn]['luns'].keys()
427 for client_image_id in client_image_ids:
f6b5b4d7
TL
428 if image_id == client_image_id and \
429 (client_iqn, client_image_id) not in deleted_client_luns:
eafe8130
TL
430 IscsiClient.instance(gateway_name=gateway_name).delete_client_lun(
431 target_iqn, client_iqn, client_image_id)
11fdf7f2
TL
432 IscsiClient.instance(gateway_name=gateway_name).delete_target_lun(target_iqn,
433 image_id)
434 pool, image = image_id.split('/', 1)
435 IscsiClient.instance(gateway_name=gateway_name).delete_disk(pool, image)
436 TaskManager.current_task().inc_progress(task_progress_inc)
20effc67
TL
437
438 @staticmethod
439 def _delete_clients(target_config, target, new_target_iqn, new_target_controls,
440 new_clients, gateway_name, target_iqn, new_groups, task_progress_inc):
441 deleted_clients = []
442 deleted_client_luns = []
443 for client_iqn, client_config in target_config['clients'].items():
444 if IscsiTarget._client_deletion_required(target, new_target_iqn, new_target_controls,
445 new_clients, client_iqn):
446 deleted_clients.append(client_iqn)
447 IscsiClient.instance(gateway_name=gateway_name).delete_client(target_iqn,
448 client_iqn)
449 else:
450 for image_id in list(client_config.get('luns', {}).keys()):
451 if IscsiTarget._client_lun_deletion_required(target, client_iqn, image_id,
452 new_clients, new_groups):
453 deleted_client_luns.append((client_iqn, image_id))
454 IscsiClient.instance(gateway_name=gateway_name).delete_client_lun(
455 target_iqn, client_iqn, image_id)
456 TaskManager.current_task().inc_progress(task_progress_inc)
457 return deleted_clients, deleted_client_luns
458
459 @staticmethod
460 def _delete_groups(target_config, target, new_target_iqn, new_target_controls,
461 new_groups, gateway_name, target_iqn, task_progress_inc):
462 for group_id in list(target_config['groups'].keys()):
463 if IscsiTarget._group_deletion_required(target, new_target_iqn, new_target_controls,
464 new_groups, group_id):
465 IscsiClient.instance(gateway_name=gateway_name).delete_group(target_iqn,
466 group_id)
467 else:
468 group = IscsiTarget._get_group(new_groups, group_id)
469
470 old_group_disks = set(target_config['groups'][group_id]['disks'].keys())
471 new_group_disks = {'{}/{}'.format(x['pool'], x['image']) for x in group['disks']}
472 local_deleted_disks = list(old_group_disks - new_group_disks)
473
474 old_group_members = set(target_config['groups'][group_id]['members'])
475 new_group_members = set(group['members'])
476 local_deleted_members = list(old_group_members - new_group_members)
477
478 if local_deleted_disks or local_deleted_members:
479 IscsiClient.instance(gateway_name=gateway_name).update_group(
480 target_iqn, group_id, local_deleted_members, local_deleted_disks)
481 TaskManager.current_task().inc_progress(task_progress_inc)
11fdf7f2
TL
482
483 @staticmethod
484 def _get_group(groups, group_id):
485 for group in groups:
486 if group['group_id'] == group_id:
487 return group
488 return None
489
490 @staticmethod
494da23a 491 def _group_deletion_required(target, new_target_iqn, new_target_controls,
f91f0fd5 492 new_groups, group_id):
494da23a 493 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
11fdf7f2
TL
494 return True
495 new_group = IscsiTarget._get_group(new_groups, group_id)
496 if not new_group:
497 return True
11fdf7f2
TL
498 return False
499
500 @staticmethod
501 def _get_client(clients, client_iqn):
502 for client in clients:
503 if client['client_iqn'] == client_iqn:
504 return client
505 return None
506
507 @staticmethod
494da23a 508 def _client_deletion_required(target, new_target_iqn, new_target_controls,
f91f0fd5 509 new_clients, client_iqn):
494da23a 510 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
11fdf7f2 511 return True
f6b5b4d7 512 new_client = IscsiTarget._get_client(new_clients, client_iqn)
11fdf7f2
TL
513 if not new_client:
514 return True
f91f0fd5
TL
515 return False
516
517 @staticmethod
518 def _client_in_group(groups, client_iqn):
519 for group in groups:
520 if client_iqn in group['members']:
11fdf7f2
TL
521 return True
522 return False
523
f6b5b4d7 524 @staticmethod
f91f0fd5 525 def _client_lun_deletion_required(target, client_iqn, image_id, new_clients, new_groups):
f6b5b4d7
TL
526 new_client = IscsiTarget._get_client(new_clients, client_iqn)
527 if not new_client:
528 return True
f91f0fd5
TL
529
530 # Disks inherited from groups must be considered
531 was_in_group = IscsiTarget._client_in_group(target['groups'], client_iqn)
532 is_in_group = IscsiTarget._client_in_group(new_groups, client_iqn)
533
534 if not was_in_group and is_in_group:
535 return True
536
537 if is_in_group:
538 return False
539
f6b5b4d7
TL
540 new_lun = IscsiTarget._get_disk(new_client.get('luns', []), image_id)
541 if not new_lun:
542 return True
f91f0fd5 543
f6b5b4d7
TL
544 old_client = IscsiTarget._get_client(target['clients'], client_iqn)
545 if not old_client:
546 return False
f91f0fd5 547
f6b5b4d7
TL
548 old_lun = IscsiTarget._get_disk(old_client.get('luns', []), image_id)
549 return new_lun != old_lun
550
11fdf7f2
TL
551 @staticmethod
552 def _get_disk(disks, image_id):
553 for disk in disks:
554 if '{}/{}'.format(disk['pool'], disk['image']) == image_id:
555 return disk
556 return None
557
558 @staticmethod
494da23a 559 def _target_lun_deletion_required(target, new_target_iqn, new_target_controls,
11fdf7f2 560 new_disks, image_id):
494da23a 561 if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
11fdf7f2
TL
562 return True
563 new_disk = IscsiTarget._get_disk(new_disks, image_id)
564 if not new_disk:
565 return True
566 old_disk = IscsiTarget._get_disk(target['disks'], image_id)
eafe8130
TL
567 new_disk_without_controls = deepcopy(new_disk)
568 new_disk_without_controls.pop('controls')
569 old_disk_without_controls = deepcopy(old_disk)
570 old_disk_without_controls.pop('controls')
571 if new_disk_without_controls != old_disk_without_controls:
11fdf7f2
TL
572 return True
573 return False
574
575 @staticmethod
494da23a
TL
576 def _target_portal_deletion_required(old_portal_host, old_portal_ip_list, new_portals_by_host):
577 if old_portal_host not in new_portals_by_host:
578 return True
579 if sorted(old_portal_ip_list) != sorted(new_portals_by_host[old_portal_host]):
580 return True
581 return False
582
583 @staticmethod
584 def _target_deletion_required(target, new_target_iqn, new_target_controls):
1911f103
TL
585 gateway = get_available_gateway()
586 settings = IscsiClient.instance(gateway_name=gateway).get_settings()
587
11fdf7f2
TL
588 if target['target_iqn'] != new_target_iqn:
589 return True
1911f103 590 if settings['api_version'] < 2 and target['target_controls'] != new_target_controls:
11fdf7f2 591 return True
11fdf7f2
TL
592 return False
593
594 @staticmethod
eafe8130 595 def _validate(target_iqn, target_controls, portals, disks, groups, settings):
11fdf7f2
TL
596 if not target_iqn:
597 raise DashboardException(msg='Target IQN is required',
598 code='target_iqn_required',
599 component='iscsi')
600
11fdf7f2
TL
601 minimum_gateways = max(1, settings['config']['minimum_gateways'])
602 portals_by_host = IscsiTarget._get_portals_by_host(portals)
603 if len(portals_by_host.keys()) < minimum_gateways:
604 if minimum_gateways == 1:
605 msg = 'At least one portal is required'
606 else:
607 msg = 'At least {} portals are required'.format(minimum_gateways)
608 raise DashboardException(msg=msg,
609 code='portals_required',
610 component='iscsi')
611
eafe8130
TL
612 # 'target_controls_limits' was introduced in ceph-iscsi > 3.2
613 # When using an older `ceph-iscsi` version these validations will
614 # NOT be executed beforehand
20effc67
TL
615 IscsiTarget._validate_target_controls_limits(settings, target_controls)
616 portal_names = [p['host'] for p in portals]
617 validate_rest_api(portal_names)
618 IscsiTarget._validate_disks(disks, settings)
619 IscsiTarget._validate_initiators(groups)
620
621 @staticmethod
622 def _validate_initiators(groups):
623 initiators = [] # type: List[Any]
624 for group in groups:
625 initiators = initiators + group['members']
626 if len(initiators) != len(set(initiators)):
627 raise DashboardException(msg='Each initiator can only be part of 1 group at a time',
628 code='initiator_in_multiple_groups',
629 component='iscsi')
630
631 @staticmethod
632 def _validate_disks(disks, settings):
633 for disk in disks:
634 pool = disk['pool']
635 image = disk['image']
636 backstore = disk['backstore']
637 required_rbd_features = settings['required_rbd_features'][backstore]
638 unsupported_rbd_features = settings['unsupported_rbd_features'][backstore]
639 IscsiTarget._validate_image(pool, image, backstore, required_rbd_features,
640 unsupported_rbd_features)
641 IscsiTarget._validate_disk_controls_limits(settings, disk, backstore)
642
643 @staticmethod
644 def _validate_disk_controls_limits(settings, disk, backstore):
645 # 'disk_controls_limits' was introduced in ceph-iscsi > 3.2
646 # When using an older `ceph-iscsi` version these validations will
647 # NOT be executed beforehand
648 if 'disk_controls_limits' in settings:
649 for disk_control_name, disk_control_value in disk['controls'].items():
650 limits = settings['disk_controls_limits'][backstore].get(disk_control_name)
651 if limits is not None:
652 min_value = limits.get('min')
653 if min_value is not None and disk_control_value < min_value:
654 raise DashboardException(msg='Disk control {} must be >= '
655 '{}'.format(disk_control_name, min_value),
656 code='disk_control_invalid_min',
657 component='iscsi')
658 max_value = limits.get('max')
659 if max_value is not None and disk_control_value > max_value:
660 raise DashboardException(msg='Disk control {} must be <= '
661 '{}'.format(disk_control_name, max_value),
662 code='disk_control_invalid_max',
663 component='iscsi')
664
665 @staticmethod
666 def _validate_target_controls_limits(settings, target_controls):
eafe8130
TL
667 if 'target_controls_limits' in settings:
668 for target_control_name, target_control_value in target_controls.items():
669 limits = settings['target_controls_limits'].get(target_control_name)
670 if limits is not None:
671 min_value = limits.get('min')
672 if min_value is not None and target_control_value < min_value:
673 raise DashboardException(msg='Target control {} must be >= '
674 '{}'.format(target_control_name, min_value),
675 code='target_control_invalid_min',
676 component='iscsi')
677 max_value = limits.get('max')
678 if max_value is not None and target_control_value > max_value:
679 raise DashboardException(msg='Target control {} must be <= '
680 '{}'.format(target_control_name, max_value),
681 code='target_control_invalid_max',
682 component='iscsi')
683
11fdf7f2 684 @staticmethod
81eedcae 685 def _validate_image(pool, image, backstore, required_rbd_features, unsupported_rbd_features):
11fdf7f2
TL
686 try:
687 ioctx = mgr.rados.open_ioctx(pool)
688 try:
689 with rbd.Image(ioctx, image) as img:
690 if img.features() & required_rbd_features != required_rbd_features:
691 raise DashboardException(msg='Image {} cannot be exported using {} '
692 'backstore because required features are '
693 'missing (required features are '
694 '{})'.format(image,
695 backstore,
696 format_bitmask(
697 required_rbd_features)),
698 code='image_missing_required_features',
699 component='iscsi')
81eedcae 700 if img.features() & unsupported_rbd_features != 0:
11fdf7f2
TL
701 raise DashboardException(msg='Image {} cannot be exported using {} '
702 'backstore because it contains unsupported '
81eedcae 703 'features ('
11fdf7f2
TL
704 '{})'.format(image,
705 backstore,
706 format_bitmask(
81eedcae 707 unsupported_rbd_features)),
11fdf7f2
TL
708 code='image_contains_unsupported_features',
709 component='iscsi')
710
711 except rbd.ImageNotFound:
712 raise DashboardException(msg='Image {} does not exist'.format(image),
713 code='image_does_not_exist',
714 component='iscsi')
715 except rados.ObjectNotFound:
716 raise DashboardException(msg='Pool {} does not exist'.format(pool),
717 code='pool_does_not_exist',
718 component='iscsi')
719
f6b5b4d7
TL
720 @staticmethod
721 def _validate_delete(gateway, target_iqn, config, new_target_iqn=None, new_target_controls=None,
722 new_disks=None, new_clients=None, new_groups=None):
723 new_target_controls = new_target_controls or {}
724 new_disks = new_disks or []
725 new_clients = new_clients or []
726 new_groups = new_groups or []
727
728 target_config = config['targets'][target_iqn]
729 target = IscsiTarget._config_to_target(target_iqn, config)
f6b5b4d7
TL
730 for client_iqn in list(target_config['clients'].keys()):
731 if IscsiTarget._client_deletion_required(target, new_target_iqn, new_target_controls,
f91f0fd5 732 new_clients, client_iqn):
f6b5b4d7
TL
733 client_info = IscsiClient.instance(gateway_name=gateway).get_clientinfo(target_iqn,
734 client_iqn)
735 if client_info.get('state', {}).get('LOGGED_IN', []):
736 raise DashboardException(msg="Client '{}' cannot be deleted until it's logged "
737 "out".format(client_iqn),
738 code='client_logged_in',
739 component='iscsi')
740
eafe8130
TL
741 @staticmethod
742 def _update_targetauth(config, target_iqn, auth, gateway_name):
743 # Target level authentication was introduced in ceph-iscsi config v11
744 if config['version'] > 10:
745 user = auth['user']
746 password = auth['password']
747 mutual_user = auth['mutual_user']
748 mutual_password = auth['mutual_password']
749 IscsiClient.instance(gateway_name=gateway_name).update_targetauth(target_iqn,
750 user,
751 password,
752 mutual_user,
753 mutual_password)
754
755 @staticmethod
756 def _update_targetacl(target_config, target_iqn, acl_enabled, gateway_name):
757 if not target_config or target_config['acl_enabled'] != acl_enabled:
758 targetauth_action = ('enable_acl' if acl_enabled else 'disable_acl')
759 IscsiClient.instance(gateway_name=gateway_name).update_targetacl(target_iqn,
760 targetauth_action)
761
1911f103 762 @staticmethod
f6b5b4d7
TL
763 def _is_auth_equal(auth_config, auth):
764 return auth['user'] == auth_config['username'] and \
765 auth['password'] == auth_config['password'] and \
766 auth['mutual_user'] == auth_config['mutual_username'] and \
767 auth['mutual_password'] == auth_config['mutual_password']
1911f103 768
11fdf7f2 769 @staticmethod
20effc67 770 @handle_request_error('iscsi')
11fdf7f2 771 def _create(target_iqn, target_controls, acl_enabled,
eafe8130
TL
772 auth, portals, disks, clients, groups,
773 task_progress_begin, task_progress_end, config, settings):
11fdf7f2
TL
774 target_config = config['targets'].get(target_iqn, None)
775 TaskManager.current_task().set_progress(task_progress_begin)
776 portals_by_host = IscsiTarget._get_portals_by_host(portals)
777 n_hosts = len(portals_by_host)
778 n_disks = len(disks)
779 n_clients = len(clients)
780 n_groups = len(groups)
781 task_progress_steps = n_hosts + n_disks + n_clients + n_groups
782 task_progress_inc = 0
783 if task_progress_steps != 0:
784 task_progress_inc = int((task_progress_end - task_progress_begin) / task_progress_steps)
20effc67
TL
785 gateway_name = portals[0]['host']
786 if not target_config:
787 IscsiClient.instance(gateway_name=gateway_name).create_target(target_iqn,
788 target_controls)
789 IscsiTarget._create_gateways(portals_by_host, target_config,
790 gateway_name, target_iqn, task_progress_inc)
791
792 update_acl = not target_config or \
793 acl_enabled != target_config['acl_enabled'] or \
794 not IscsiTarget._is_auth_equal(target_config['auth'], auth)
795 if update_acl:
796 IscsiTarget._update_acl(acl_enabled, config, target_iqn,
797 auth, gateway_name, target_config)
798
799 IscsiTarget._create_disks(disks, config, gateway_name, target_config,
800 target_iqn, settings, task_progress_inc)
801 IscsiTarget._create_clients(clients, target_config, gateway_name,
802 target_iqn, groups, task_progress_inc)
803 IscsiTarget._create_groups(groups, target_config, gateway_name,
804 target_iqn, task_progress_inc, target_controls,
805 task_progress_end)
806
807 @staticmethod
808 def _update_acl(acl_enabled, config, target_iqn, auth, gateway_name, target_config):
809 if acl_enabled:
810 IscsiTarget._update_targetauth(config, target_iqn, auth, gateway_name)
811 IscsiTarget._update_targetacl(target_config, target_iqn, acl_enabled,
812 gateway_name)
813 else:
814 IscsiTarget._update_targetacl(target_config, target_iqn, acl_enabled,
815 gateway_name)
816 IscsiTarget._update_targetauth(config, target_iqn, auth, gateway_name)
817
818 @staticmethod
819 def _create_gateways(portals_by_host, target_config, gateway_name, target_iqn,
820 task_progress_inc):
821 for host, ip_list in portals_by_host.items():
822 if not target_config or host not in target_config['portals']:
823 IscsiClient.instance(gateway_name=gateway_name).create_gateway(target_iqn,
824 host,
825 ip_list)
826 TaskManager.current_task().inc_progress(task_progress_inc)
827
828 @staticmethod
829 def _create_groups(groups, target_config, gateway_name, target_iqn, task_progress_inc,
830 target_controls, task_progress_end):
831 for group in groups:
832 group_id = group['group_id']
833 members = group['members']
834 image_ids = []
835 for disk in group['disks']:
836 image_ids.append('{}/{}'.format(disk['pool'], disk['image']))
837
838 if target_config and group_id in target_config['groups']:
839 old_members = target_config['groups'][group_id]['members']
840 old_disks = target_config['groups'][group_id]['disks'].keys()
841
842 if not target_config or group_id not in target_config['groups'] or \
843 list(set(group['members']) - set(old_members)) or \
844 list(set(image_ids) - set(old_disks)):
845 IscsiClient.instance(gateway_name=gateway_name).create_group(
846 target_iqn, group_id, members, image_ids)
847 TaskManager.current_task().inc_progress(task_progress_inc)
848 if target_controls:
849 if not target_config or target_controls != target_config['controls']:
850 IscsiClient.instance(gateway_name=gateway_name).reconfigure_target(
851 target_iqn, target_controls)
852 TaskManager.current_task().set_progress(task_progress_end)
853
854 @staticmethod
855 def _create_clients(clients, target_config, gateway_name, target_iqn, groups,
856 task_progress_inc):
857 for client in clients:
858 client_iqn = client['client_iqn']
859 if not target_config or client_iqn not in target_config['clients']:
860 IscsiClient.instance(gateway_name=gateway_name).create_client(target_iqn,
861 client_iqn)
862 if not target_config or client_iqn not in target_config['clients'] or \
863 not IscsiTarget._is_auth_equal(target_config['clients'][client_iqn]['auth'],
864 client['auth']):
865 user = client['auth']['user']
866 password = client['auth']['password']
867 m_user = client['auth']['mutual_user']
868 m_password = client['auth']['mutual_password']
869 IscsiClient.instance(gateway_name=gateway_name).create_client_auth(
870 target_iqn, client_iqn, user, password, m_user, m_password)
871 for lun in client['luns']:
872 pool = lun['pool']
873 image = lun['image']
11fdf7f2 874 image_id = '{}/{}'.format(pool, image)
20effc67
TL
875 # Disks inherited from groups must be considered
876 group_disks = []
877 for group in groups:
878 if client_iqn in group['members']:
879 group_disks = ['{}/{}'.format(x['pool'], x['image'])
880 for x in group['disks']]
f6b5b4d7 881 if not target_config or client_iqn not in target_config['clients'] or \
20effc67
TL
882 (image_id not in target_config['clients'][client_iqn]['luns']
883 and image_id not in group_disks):
884 IscsiClient.instance(gateway_name=gateway_name).create_client_lun(
885 target_iqn, client_iqn, image_id)
886 TaskManager.current_task().inc_progress(task_progress_inc)
887
888 @staticmethod
889 def _create_disks(disks, config, gateway_name, target_config, target_iqn, settings,
890 task_progress_inc):
891 for disk in disks:
892 pool = disk['pool']
893 image = disk['image']
894 image_id = '{}/{}'.format(pool, image)
895 backstore = disk['backstore']
896 wwn = disk.get('wwn')
897 lun = disk.get('lun')
898 if image_id not in config['disks']:
899 IscsiClient.instance(gateway_name=gateway_name).create_disk(pool,
900 image,
901 backstore,
902 wwn)
903 if not target_config or image_id not in target_config['disks']:
904 IscsiClient.instance(gateway_name=gateway_name).create_target_lun(target_iqn,
905 image_id,
906 lun)
907
908 controls = disk['controls']
909 d_conf_controls = {}
910 if image_id in config['disks']:
911 d_conf_controls = config['disks'][image_id]['controls']
912 disk_default_controls = settings['disk_default_controls'][backstore]
913 for old_control in d_conf_controls.keys():
914 # If control was removed, restore the default value
915 if old_control not in controls:
916 controls[old_control] = disk_default_controls[old_control]
917
918 if (image_id not in config['disks'] or d_conf_controls != controls) and controls:
919 IscsiClient.instance(gateway_name=gateway_name).reconfigure_disk(pool,
920 image,
921 controls)
922 TaskManager.current_task().inc_progress(task_progress_inc)
11fdf7f2
TL
923
924 @staticmethod
925 def _config_to_target(target_iqn, config):
926 target_config = config['targets'][target_iqn]
927 portals = []
494da23a
TL
928 for host, portal_config in target_config['portals'].items():
929 for portal_ip in portal_config['portal_ip_addresses']:
11fdf7f2
TL
930 portal = {
931 'host': host,
932 'ip': portal_ip
933 }
934 portals.append(portal)
935 portals = IscsiTarget._sorted_portals(portals)
936 disks = []
937 for target_disk in target_config['disks']:
938 disk_config = config['disks'][target_disk]
939 disk = {
940 'pool': disk_config['pool'],
941 'image': disk_config['image'],
942 'controls': disk_config['controls'],
eafe8130
TL
943 'backstore': disk_config['backstore'],
944 'wwn': disk_config['wwn']
11fdf7f2 945 }
eafe8130
TL
946 # lun_id was introduced in ceph-iscsi config v11
947 if config['version'] > 10:
948 disk['lun'] = target_config['disks'][target_disk]['lun_id']
11fdf7f2
TL
949 disks.append(disk)
950 disks = IscsiTarget._sorted_disks(disks)
951 clients = []
952 for client_iqn, client_config in target_config['clients'].items():
953 luns = []
954 for client_lun in client_config['luns'].keys():
955 pool, image = client_lun.split('/', 1)
956 lun = {
957 'pool': pool,
958 'image': image
959 }
960 luns.append(lun)
961 user = client_config['auth']['username']
962 password = client_config['auth']['password']
963 mutual_user = client_config['auth']['mutual_username']
964 mutual_password = client_config['auth']['mutual_password']
965 client = {
966 'client_iqn': client_iqn,
967 'luns': luns,
968 'auth': {
969 'user': user,
970 'password': password,
971 'mutual_user': mutual_user,
972 'mutual_password': mutual_password
973 }
974 }
975 clients.append(client)
976 clients = IscsiTarget._sorted_clients(clients)
977 groups = []
978 for group_id, group_config in target_config['groups'].items():
979 group_disks = []
980 for group_disk_key, _ in group_config['disks'].items():
981 pool, image = group_disk_key.split('/', 1)
982 group_disk = {
983 'pool': pool,
984 'image': image
985 }
986 group_disks.append(group_disk)
987 group = {
988 'group_id': group_id,
989 'disks': group_disks,
990 'members': group_config['members'],
991 }
992 groups.append(group)
993 groups = IscsiTarget._sorted_groups(groups)
994 target_controls = target_config['controls']
11fdf7f2
TL
995 acl_enabled = target_config['acl_enabled']
996 target = {
997 'target_iqn': target_iqn,
998 'portals': portals,
999 'disks': disks,
1000 'clients': clients,
1001 'groups': groups,
1002 'target_controls': target_controls,
1003 'acl_enabled': acl_enabled
1004 }
eafe8130
TL
1005 # Target level authentication was introduced in ceph-iscsi config v11
1006 if config['version'] > 10:
1007 target_user = target_config['auth']['username']
1008 target_password = target_config['auth']['password']
1009 target_mutual_user = target_config['auth']['mutual_username']
1010 target_mutual_password = target_config['auth']['mutual_password']
1011 target['auth'] = {
1012 'user': target_user,
1013 'password': target_password,
1014 'mutual_user': target_mutual_user,
1015 'mutual_password': target_mutual_password
1016 }
11fdf7f2
TL
1017 return target
1018
eafe8130
TL
1019 @staticmethod
1020 def _is_executing(target_iqn):
1021 executing_tasks, _ = TaskManager.list()
1022 for t in executing_tasks:
1023 if t.name.startswith('iscsi/target') and t.metadata.get('target_iqn') == target_iqn:
1024 return True
1025 return False
1026
11fdf7f2
TL
1027 @staticmethod
1028 def _set_info(target):
1029 if not target['portals']:
1030 return
1031 target_iqn = target['target_iqn']
eafe8130
TL
1032 # During task execution, additional info is not available
1033 if IscsiTarget._is_executing(target_iqn):
1034 return
92f5a8d4
TL
1035 # If any portal is down, additional info is not available
1036 for portal in target['portals']:
1037 try:
1038 IscsiClient.instance(gateway_name=portal['host']).ping()
1039 except (IscsiGatewayDoesNotExist, RequestException):
1040 return
11fdf7f2 1041 gateway_name = target['portals'][0]['host']
eafe8130
TL
1042 try:
1043 target_info = IscsiClient.instance(gateway_name=gateway_name).get_targetinfo(
1044 target_iqn)
1045 target['info'] = target_info
1046 for client in target['clients']:
1047 client_iqn = client['client_iqn']
1048 client_info = IscsiClient.instance(gateway_name=gateway_name).get_clientinfo(
1049 target_iqn, client_iqn)
1050 client['info'] = client_info
1051 except RequestException as e:
1052 # Target/Client has been removed in the meanwhile (e.g. using gwcli)
1053 if e.status_code != 404:
1054 raise e
11fdf7f2
TL
1055
1056 @staticmethod
1057 def _sorted_portals(portals):
1058 portals = portals or []
1059 return sorted(portals, key=lambda p: '{}.{}'.format(p['host'], p['ip']))
1060
1061 @staticmethod
1062 def _sorted_disks(disks):
1063 disks = disks or []
1064 return sorted(disks, key=lambda d: '{}.{}'.format(d['pool'], d['image']))
1065
1066 @staticmethod
1067 def _sorted_clients(clients):
1068 clients = clients or []
1069 for client in clients:
1070 client['luns'] = sorted(client['luns'],
1071 key=lambda d: '{}.{}'.format(d['pool'], d['image']))
1072 return sorted(clients, key=lambda c: c['client_iqn'])
1073
1074 @staticmethod
1075 def _sorted_groups(groups):
1076 groups = groups or []
1077 for group in groups:
1078 group['disks'] = sorted(group['disks'],
1079 key=lambda d: '{}.{}'.format(d['pool'], d['image']))
1080 group['members'] = sorted(group['members'])
1081 return sorted(groups, key=lambda g: g['group_id'])
1082
1083 @staticmethod
1084 def _get_portals_by_host(portals):
9f95a23c
TL
1085 # type: (List[dict]) -> Dict[str, List[str]]
1086 portals_by_host = {} # type: Dict[str, List[str]]
11fdf7f2
TL
1087 for portal in portals:
1088 host = portal['host']
1089 ip = portal['ip']
1090 if host not in portals_by_host:
1091 portals_by_host[host] = []
1092 portals_by_host[host].append(ip)
1093 return portals_by_host
92f5a8d4
TL
1094
1095
1096def get_available_gateway():
1097 gateways = IscsiGatewaysConfig.get_gateways_config()['gateways']
1098 if not gateways:
1099 raise DashboardException(msg='There are no gateways defined',
1100 code='no_gateways_defined',
1101 component='iscsi')
1102 for gateway in gateways:
1103 try:
1104 IscsiClient.instance(gateway_name=gateway).ping()
1105 return gateway
1106 except RequestException:
1107 pass
1108 raise DashboardException(msg='There are no gateways available',
1109 code='no_gateways_available',
1110 component='iscsi')
1111
1112
1113def validate_rest_api(gateways):
1114 for gateway in gateways:
1115 try:
1116 IscsiClient.instance(gateway_name=gateway).ping()
1117 except RequestException:
1118 raise DashboardException(msg='iSCSI REST Api not available for gateway '
1119 '{}'.format(gateway),
1120 code='ceph_iscsi_rest_api_not_available_for_gateway',
1121 component='iscsi')
1911f103
TL
1122
1123
1124def validate_auth(auth):
1125 username_regex = re.compile(r'^[\w\.:@_-]{8,64}$')
1126 password_regex = re.compile(r'^[\w@\-_\/]{12,16}$')
1127 result = True
1128
1129 if auth['user'] or auth['password']:
1130 result = bool(username_regex.match(auth['user'])) and \
1131 bool(password_regex.match(auth['password']))
1132
1133 if auth['mutual_user'] or auth['mutual_password']:
1134 result = result and bool(username_regex.match(auth['mutual_user'])) and \
1135 bool(password_regex.match(auth['mutual_password'])) and auth['user']
1136
1137 if not result:
1138 raise DashboardException(msg='Bad authentication',
1139 code='target_bad_auth',
1140 component='iscsi')