]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/pybind/mgr/dashboard/controllers/iscsi.py
import 14.2.4 nautilus point release
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / iscsi.py
index f8b0e3063b5ef93aae7634a1ba98bba2f45a210e..df44f889807047039fdaf25b702dbdd8610cd5bc 100644 (file)
@@ -25,7 +25,7 @@ from ..tools import TaskManager
 @UiApiController('/iscsi', Scope.ISCSI)
 class IscsiUi(BaseController):
 
-    REQUIRED_CEPH_ISCSI_CONFIG_VERSION = 8
+    REQUIRED_CEPH_ISCSI_CONFIG_VERSION = 10
 
     @Endpoint()
     @ReadPermission
@@ -36,7 +36,7 @@ class IscsiUi(BaseController):
             status['message'] = 'There are no gateways defined'
             return status
         try:
-            for gateway in gateways.keys():
+            for gateway in gateways:
                 try:
                     IscsiClient.instance(gateway_name=gateway).ping()
                 except RequestException:
@@ -51,10 +51,14 @@ class IscsiUi(BaseController):
             status['available'] = True
         except RequestException as e:
             if e.content:
-                content = json.loads(e.content)
-                content_message = content.get('message')
+                try:
+                    content = json.loads(e.content)
+                    content_message = content.get('message')
+                except ValueError:
+                    content_message = e.content
                 if content_message:
                     status['message'] = content_message
+
         return status
 
     @Endpoint()
@@ -67,7 +71,7 @@ class IscsiUi(BaseController):
     def portals(self):
         portals = []
         gateways_config = IscsiGatewaysConfig.get_gateways_config()
-        for name in gateways_config['gateways'].keys():
+        for name in gateways_config['gateways']:
             ip_addresses = IscsiClient.instance(gateway_name=name).get_ip_addresses()
             portals.append({'name': name, 'ip_addresses': ip_addresses['data']})
         return sorted(portals, key=lambda p: '{}.{}'.format(p['name'], p['ip_addresses']))
@@ -144,7 +148,7 @@ class IscsiUi(BaseController):
 class Iscsi(BaseController):
 
     @Endpoint('GET', 'discoveryauth')
-    @UpdatePermission
+    @ReadPermission
     def get_discoveryauth(self):
         return self._get_discoveryauth()
 
@@ -219,7 +223,7 @@ class IscsiTarget(RESTController):
             raise DashboardException(msg='Target already exists',
                                      code='target_already_exists',
                                      component='iscsi')
-        IscsiTarget._validate(target_iqn, portals, disks)
+        IscsiTarget._validate(target_iqn, portals, disks, groups)
         IscsiTarget._create(target_iqn, target_controls, acl_enabled, portals, disks, clients,
                             groups, 0, 100, config)
 
@@ -241,7 +245,7 @@ class IscsiTarget(RESTController):
             raise DashboardException(msg='Target IQN already in use',
                                      code='target_iqn_already_in_use',
                                      component='iscsi')
-        IscsiTarget._validate(new_target_iqn, portals, disks)
+        IscsiTarget._validate(new_target_iqn, portals, disks, groups)
         config = IscsiTarget._delete(target_iqn, config, 0, 50, new_target_iqn, target_controls,
                                      portals, disks, clients, groups)
         IscsiTarget._create(new_target_iqn, target_controls, acl_enabled, portals, disks, clients,
@@ -275,7 +279,7 @@ class IscsiTarget(RESTController):
         deleted_groups = []
         for group_id in list(target_config['groups'].keys()):
             if IscsiTarget._group_deletion_required(target, new_target_iqn, new_target_controls,
-                                                    new_portals, new_groups, group_id, new_clients,
+                                                    new_groups, group_id, new_clients,
                                                     new_disks):
                 deleted_groups.append(group_id)
                 IscsiClient.instance(gateway_name=gateway_name).delete_group(target_iqn,
@@ -283,22 +287,29 @@ class IscsiTarget(RESTController):
             TaskManager.current_task().inc_progress(task_progress_inc)
         for client_iqn in list(target_config['clients'].keys()):
             if IscsiTarget._client_deletion_required(target, new_target_iqn, new_target_controls,
-                                                     new_portals, new_clients, client_iqn,
+                                                     new_clients, client_iqn,
                                                      new_groups, deleted_groups):
                 IscsiClient.instance(gateway_name=gateway_name).delete_client(target_iqn,
                                                                               client_iqn)
             TaskManager.current_task().inc_progress(task_progress_inc)
         for image_id in target_config['disks']:
             if IscsiTarget._target_lun_deletion_required(target, new_target_iqn,
-                                                         new_target_controls, new_portals,
+                                                         new_target_controls,
                                                          new_disks, image_id):
                 IscsiClient.instance(gateway_name=gateway_name).delete_target_lun(target_iqn,
                                                                                   image_id)
                 pool, image = image_id.split('/', 1)
                 IscsiClient.instance(gateway_name=gateway_name).delete_disk(pool, image)
             TaskManager.current_task().inc_progress(task_progress_inc)
-        if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
-                                                 new_portals):
+        old_portals_by_host = IscsiTarget._get_portals_by_host(target['portals'])
+        new_portals_by_host = IscsiTarget._get_portals_by_host(new_portals)
+        for old_portal_host, old_portal_ip_list in old_portals_by_host.items():
+            if IscsiTarget._target_portal_deletion_required(old_portal_host,
+                                                            old_portal_ip_list,
+                                                            new_portals_by_host):
+                IscsiClient.instance(gateway_name=gateway_name).delete_gateway(target_iqn,
+                                                                               old_portal_host)
+        if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
             IscsiClient.instance(gateway_name=gateway_name).delete_target(target_iqn)
         TaskManager.current_task().set_progress(task_progress_end)
         return IscsiClient.instance(gateway_name=gateway_name).get_config()
@@ -311,10 +322,9 @@ class IscsiTarget(RESTController):
         return None
 
     @staticmethod
-    def _group_deletion_required(target, new_target_iqn, new_target_controls, new_portals,
+    def _group_deletion_required(target, new_target_iqn, new_target_controls,
                                  new_groups, group_id, new_clients, new_disks):
-        if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
-                                                 new_portals):
+        if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
             return True
         new_group = IscsiTarget._get_group(new_groups, group_id)
         if not new_group:
@@ -325,14 +335,14 @@ class IscsiTarget(RESTController):
         # Check if any client inside this group has changed
         for client_iqn in new_group['members']:
             if IscsiTarget._client_deletion_required(target, new_target_iqn, new_target_controls,
-                                                     new_portals, new_clients, client_iqn,
+                                                     new_clients, client_iqn,
                                                      new_groups, []):
                 return True
         # Check if any disk inside this group has changed
         for disk in new_group['disks']:
             image_id = '{}/{}'.format(disk['pool'], disk['image'])
             if IscsiTarget._target_lun_deletion_required(target, new_target_iqn,
-                                                         new_target_controls, new_portals,
+                                                         new_target_controls,
                                                          new_disks, image_id):
                 return True
         return False
@@ -345,10 +355,9 @@ class IscsiTarget(RESTController):
         return None
 
     @staticmethod
-    def _client_deletion_required(target, new_target_iqn, new_target_controls, new_portals,
+    def _client_deletion_required(target, new_target_iqn, new_target_controls,
                                   new_clients, client_iqn, new_groups, deleted_groups):
-        if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
-                                                 new_portals):
+        if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
             return True
         new_client = deepcopy(IscsiTarget._get_client(new_clients, client_iqn))
         if not new_client:
@@ -374,10 +383,9 @@ class IscsiTarget(RESTController):
         return None
 
     @staticmethod
-    def _target_lun_deletion_required(target, new_target_iqn, new_target_controls, new_portals,
+    def _target_lun_deletion_required(target, new_target_iqn, new_target_controls,
                                       new_disks, image_id):
-        if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
-                                                 new_portals):
+        if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls):
             return True
         new_disk = IscsiTarget._get_disk(new_disks, image_id)
         if not new_disk:
@@ -388,17 +396,23 @@ class IscsiTarget(RESTController):
         return False
 
     @staticmethod
-    def _target_deletion_required(target, new_target_iqn, new_target_controls, new_portals):
+    def _target_portal_deletion_required(old_portal_host, old_portal_ip_list, new_portals_by_host):
+        if old_portal_host not in new_portals_by_host:
+            return True
+        if sorted(old_portal_ip_list) != sorted(new_portals_by_host[old_portal_host]):
+            return True
+        return False
+
+    @staticmethod
+    def _target_deletion_required(target, new_target_iqn, new_target_controls):
         if target['target_iqn'] != new_target_iqn:
             return True
         if target['target_controls'] != new_target_controls:
             return True
-        if target['portals'] != new_portals:
-            return True
         return False
 
     @staticmethod
-    def _validate(target_iqn, portals, disks):
+    def _validate(target_iqn, portals, disks, groups):
         if not target_iqn:
             raise DashboardException(msg='Target IQN is required',
                                      code='target_iqn_required',
@@ -431,12 +445,20 @@ class IscsiTarget(RESTController):
             image = disk['image']
             backstore = disk['backstore']
             required_rbd_features = settings['required_rbd_features'][backstore]
-            supported_rbd_features = settings['supported_rbd_features'][backstore]
+            unsupported_rbd_features = settings['unsupported_rbd_features'][backstore]
             IscsiTarget._validate_image(pool, image, backstore, required_rbd_features,
-                                        supported_rbd_features)
+                                        unsupported_rbd_features)
+
+        initiators = []
+        for group in groups:
+            initiators = initiators + group['members']
+        if len(initiators) != len(set(initiators)):
+            raise DashboardException(msg='Each initiator can only be part of 1 group at a time',
+                                     code='initiator_in_multiple_groups',
+                                     component='iscsi')
 
     @staticmethod
-    def _validate_image(pool, image, backstore, required_rbd_features, supported_rbd_features):
+    def _validate_image(pool, image, backstore, required_rbd_features, unsupported_rbd_features):
         try:
             ioctx = mgr.rados.open_ioctx(pool)
             try:
@@ -451,14 +473,14 @@ class IscsiTarget(RESTController):
                                                                       required_rbd_features)),
                                                  code='image_missing_required_features',
                                                  component='iscsi')
-                    if img.features() & supported_rbd_features != img.features():
+                    if img.features() & unsupported_rbd_features != 0:
                         raise DashboardException(msg='Image {} cannot be exported using {} '
                                                      'backstore because it contains unsupported '
-                                                     'features (supported features are '
+                                                     'features ('
                                                      '{})'.format(image,
                                                                   backstore,
                                                                   format_bitmask(
-                                                                      supported_rbd_features)),
+                                                                      unsupported_rbd_features)),
                                                  code='image_contains_unsupported_features',
                                                  component='iscsi')
 
@@ -491,11 +513,12 @@ class IscsiTarget(RESTController):
             if not target_config:
                 IscsiClient.instance(gateway_name=gateway_name).create_target(target_iqn,
                                                                               target_controls)
-                for host, ip_list in portals_by_host.items():
+            for host, ip_list in portals_by_host.items():
+                if not target_config or host not in target_config['portals']:
                     IscsiClient.instance(gateway_name=gateway_name).create_gateway(target_iqn,
                                                                                    host,
                                                                                    ip_list)
-                    TaskManager.current_task().inc_progress(task_progress_inc)
+                TaskManager.current_task().inc_progress(task_progress_inc)
             targetauth_action = ('enable_acl' if acl_enabled else 'disable_acl')
             IscsiClient.instance(gateway_name=gateway_name).update_targetauth(target_iqn,
                                                                               targetauth_action)
@@ -562,10 +585,8 @@ class IscsiTarget(RESTController):
     def _config_to_target(target_iqn, config):
         target_config = config['targets'][target_iqn]
         portals = []
-        for host in target_config['portals'].keys():
-            ips = IscsiClient.instance(gateway_name=host).get_ip_addresses()['data']
-            portal_ips = [ip for ip in ips if ip in target_config['ip_list']]
-            for portal_ip in portal_ips:
+        for host, portal_config in target_config['portals'].items():
+            for portal_ip in portal_config['portal_ip_addresses']:
                 portal = {
                     'host': host,
                     'ip': portal_ip
@@ -650,6 +671,11 @@ class IscsiTarget(RESTController):
         gateway_name = target['portals'][0]['host']
         target_info = IscsiClient.instance(gateway_name=gateway_name).get_targetinfo(target_iqn)
         target['info'] = target_info
+        for client in target['clients']:
+            client_iqn = client['client_iqn']
+            client_info = IscsiClient.instance(gateway_name=gateway_name).get_clientinfo(
+                target_iqn, client_iqn)
+            client['info'] = client_info
 
     @staticmethod
     def _sorted_portals(portals):