]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Support iSCSI target-level CHAP auth
authorRicardo Marques <rimarques@suse.com>
Thu, 29 Aug 2019 14:28:23 +0000 (15:28 +0100)
committerRicardo Marques <rimarques@suse.com>
Thu, 26 Sep 2019 13:43:32 +0000 (14:43 +0100)
Requires `ceph-iscsi` config v11

Fixes: https://tracker.ceph.com/issues/41576
Signed-off-by: Ricardo Marques <rimarques@suse.com>
(cherry picked from commit 6cc6e8d5dba658ebfa87fe2ab9ec6e375cb11665)

 Conflicts:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.ts
        src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.html

Conflicts caused by "Master" using Bootstrap 4, but "Nautilus" using Bootstap 3.

src/pybind/mgr/dashboard/controllers/iscsi.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/iscsi.service.ts
src/pybind/mgr/dashboard/services/iscsi_client.py
src/pybind/mgr/dashboard/tests/test_iscsi.py

index 15cfd750069becf61b8e6bb79131e146e222e9ea..e6f51888f3191da228b2899d486b9799f51ca26e 100644 (file)
@@ -25,7 +25,8 @@ from ..tools import TaskManager
 @UiApiController('/iscsi', Scope.ISCSI)
 class IscsiUi(BaseController):
 
-    REQUIRED_CEPH_ISCSI_CONFIG_VERSION = 10
+    REQUIRED_CEPH_ISCSI_CONFIG_MIN_VERSION = 10
+    REQUIRED_CEPH_ISCSI_CONFIG_MAX_VERSION = 11
 
     @Endpoint()
     @ReadPermission
@@ -43,10 +44,13 @@ class IscsiUi(BaseController):
                     status['message'] = 'Gateway {} is inaccessible'.format(gateway)
                     return status
             config = IscsiClient.instance().get_config()
-            if config['version'] != IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_VERSION:
-                status['message'] = 'Unsupported `ceph-iscsi` config version. Expected {} but ' \
-                                    'found {}.'.format(IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_VERSION,
-                                                       config['version'])
+            if config['version'] < IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_MIN_VERSION or \
+                    config['version'] > IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_MAX_VERSION:
+                status['message'] = 'Unsupported `ceph-iscsi` config version. ' \
+                                    'Expected >= {} and <= {} but found' \
+                                    ' {}.'.format(IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_MIN_VERSION,
+                                                  IscsiUi.REQUIRED_CEPH_ISCSI_CONFIG_MAX_VERSION,
+                                                  config['version'])
                 return status
             status['available'] = True
         except RequestException as e:
@@ -61,6 +65,13 @@ class IscsiUi(BaseController):
 
         return status
 
+    @Endpoint()
+    @ReadPermission
+    def version(self):
+        return {
+            'ceph_iscsi_config_version': IscsiClient.instance().get_config()['version']
+        }
+
     @Endpoint()
     @ReadPermission
     def settings(self):
@@ -211,7 +222,7 @@ class IscsiTarget(RESTController):
 
     @iscsi_target_task('create', {'target_iqn': '{target_iqn}'})
     def create(self, target_iqn=None, target_controls=None, acl_enabled=None,
-               portals=None, disks=None, clients=None, groups=None):
+               auth=None, portals=None, disks=None, clients=None, groups=None):
         target_controls = target_controls or {}
         portals = portals or []
         disks = disks or []
@@ -225,12 +236,13 @@ class IscsiTarget(RESTController):
                                      component='iscsi')
         settings = IscsiClient.instance().get_settings()
         IscsiTarget._validate(target_iqn, target_controls, portals, disks, groups, settings)
-        IscsiTarget._create(target_iqn, target_controls, acl_enabled, portals, disks, clients,
-                            groups, 0, 100, config, settings)
+
+        IscsiTarget._create(target_iqn, target_controls, acl_enabled, auth, portals, disks,
+                            clients, groups, 0, 100, config, settings)
 
     @iscsi_target_task('edit', {'target_iqn': '{target_iqn}'})
     def set(self, target_iqn, new_target_iqn=None, target_controls=None, acl_enabled=None,
-            portals=None, disks=None, clients=None, groups=None):
+            auth=None, portals=None, disks=None, clients=None, groups=None):
         target_controls = target_controls or {}
         portals = IscsiTarget._sorted_portals(portals)
         disks = IscsiTarget._sorted_disks(disks)
@@ -250,8 +262,8 @@ class IscsiTarget(RESTController):
         IscsiTarget._validate(new_target_iqn, target_controls, portals, disks, groups, settings)
         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,
-                            groups, 50, 100, config, settings)
+        IscsiTarget._create(new_target_iqn, target_controls, acl_enabled, auth, portals, disks,
+                            clients, groups, 50, 100, config, settings)
 
     @staticmethod
     def _delete(target_iqn, config, task_progress_begin, task_progress_end, new_target_iqn=None,
@@ -538,9 +550,30 @@ class IscsiTarget(RESTController):
                                      code='pool_does_not_exist',
                                      component='iscsi')
 
+    @staticmethod
+    def _update_targetauth(config, target_iqn, auth, gateway_name):
+        # Target level authentication was introduced in ceph-iscsi config v11
+        if config['version'] > 10:
+            user = auth['user']
+            password = auth['password']
+            mutual_user = auth['mutual_user']
+            mutual_password = auth['mutual_password']
+            IscsiClient.instance(gateway_name=gateway_name).update_targetauth(target_iqn,
+                                                                              user,
+                                                                              password,
+                                                                              mutual_user,
+                                                                              mutual_password)
+
+    @staticmethod
+    def _update_targetacl(target_config, target_iqn, acl_enabled, gateway_name):
+        if not target_config or target_config['acl_enabled'] != acl_enabled:
+            targetauth_action = ('enable_acl' if acl_enabled else 'disable_acl')
+            IscsiClient.instance(gateway_name=gateway_name).update_targetacl(target_iqn,
+                                                                             targetauth_action)
+
     @staticmethod
     def _create(target_iqn, target_controls, acl_enabled,
-                portals, disks, clients, groups,
+                auth, portals, disks, clients, groups,
                 task_progress_begin, task_progress_end, config, settings):
         target_config = config['targets'].get(target_iqn, None)
         TaskManager.current_task().set_progress(task_progress_begin)
@@ -564,9 +597,15 @@ class IscsiTarget(RESTController):
                                                                                    host,
                                                                                    ip_list)
                 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)
+
+            if acl_enabled:
+                IscsiTarget._update_targetauth(config, target_iqn, auth, gateway_name)
+                IscsiTarget._update_targetacl(target_config, target_iqn, acl_enabled, gateway_name)
+
+            else:
+                IscsiTarget._update_targetacl(target_config, target_iqn, acl_enabled, gateway_name)
+                IscsiTarget._update_targetauth(config, target_iqn, auth, gateway_name)
+
             for disk in disks:
                 pool = disk['pool']
                 image = disk['image']
@@ -716,6 +755,18 @@ class IscsiTarget(RESTController):
             'target_controls': target_controls,
             'acl_enabled': acl_enabled
         }
+        # Target level authentication was introduced in ceph-iscsi config v11
+        if config['version'] > 10:
+            target_user = target_config['auth']['username']
+            target_password = target_config['auth']['password']
+            target_mutual_user = target_config['auth']['mutual_username']
+            target_mutual_password = target_config['auth']['mutual_password']
+            target['auth'] = {
+                'user': target_user,
+                'password': target_password,
+                'mutual_user': target_mutual_user,
+                'mutual_password': target_mutual_password
+            }
         return target
 
     @staticmethod
index fa5c2c267be9f95f9f335d729059c62de448e2ef..51c81ce83cbb09edbf6e49dd4c95a7ad5436f0f1 100644 (file)
@@ -19,6 +19,8 @@ export class IscsiTargetDetailsComponent implements OnChanges, OnInit {
   selection: CdTableSelection;
   @Input()
   settings: any;
+  @Input()
+  cephIscsiConfigVersion: number;
 
   @ViewChild('highlightTpl')
   highlightTpl: TemplateRef<any>;
@@ -74,8 +76,12 @@ export class IscsiTargetDetailsComponent implements OnChanges, OnInit {
   }
 
   private generateTree() {
-    this.metadata = { root: this.selectedItem.target_controls };
-
+    const target_meta = _.cloneDeep(this.selectedItem.target_controls);
+    // Target level authentication was introduced in ceph-iscsi config v11
+    if (this.cephIscsiConfigVersion > 10) {
+      _.extend(target_meta, _.cloneDeep(this.selectedItem.auth));
+    }
+    this.metadata = { root: target_meta };
     const cssClasses = {
       target: {
         expanded: this.selectedItem.cdExecuting
@@ -253,6 +259,16 @@ export class IscsiTargetDetailsComponent implements OnChanges, OnInit {
             current: tempData[key] || value
           };
         });
+        // Target level authentication was introduced in ceph-iscsi config v11
+        if (this.cephIscsiConfigVersion > 10) {
+          ['user', 'password', 'mutual_user', 'mutual_password'].forEach((key) => {
+            this.data.push({
+              displayName: key,
+              default: null,
+              current: tempData[key]
+            });
+          });
+        }
       } else if (e.node.id.toString().startsWith('disk_')) {
         this.columns[2].isHidden = false;
         this.data = _.map(this.settings.disk_default_controls[tempData.backstore], (value, key) => {
index 5a7031dd4c0de6e4feb2246d0fe61638e8734a74..6944dda3c6f7afe2d231409dc8271a7d65512c12 100644 (file)
           </div>
         </div>
 
+        <!-- Target level authentication was introduced in ceph-iscsi config v11 -->
+        <div formGroupName="auth" *ngIf="cephIscsiConfigVersion > 10 && !targetForm.getValue('acl_enabled')">
+
+          <!-- Target user -->
+          <div class="form-group"
+               [ngClass]="{'has-error': targetForm.showError('user', formDir)}">
+            <label class="control-label col-sm-3"
+                   for="target_user">
+              <ng-container i18n>User</ng-container>
+            </label>
+            <div class="col-sm-9">
+              <input class="form-control"
+                     type="text"
+                     id="target_user"
+                     name="target_user"
+                     formControlName="user" />
+
+              <span class="help-block"
+                    *ngIf="targetForm.showError('user', formDir, 'required')"
+                    i18n>This field is required.</span>
+
+              <span class="help-block"
+                    *ngIf="targetForm.showError('user', formDir, 'pattern')"
+                    i18n>Usernames must have a length of 8 to 64 characters and
+                can only contain letters, '.', '@', '-', '_' or ':'.</span>
+            </div>
+          </div>
+
+          <!-- Target password -->
+          <div class="form-group"
+               [ngClass]="{'has-error': targetForm.showError('password', formDir)}">
+            <label class="control-label col-sm-3"
+                   for="target_password">
+              <ng-container i18n>Password</ng-container>
+            </label>
+            <div class="col-sm-9">
+              <div class="input-group">
+                <input class="form-control"
+                       type="password"
+                       autocomplete="new-password"
+                       id="target_password"
+                       name="target_password"
+                       formControlName="password" />
+
+                <span class="input-group-btn">
+                  <button type="button"
+                          class="btn btn-default"
+                          cdPasswordButton="target_password">
+                  </button>
+                  <button type="button"
+                          class="btn btn-default"
+                          cdCopy2ClipboardButton="target_password">
+                  </button>
+                </span>
+              </div>
+
+              <span class="help-block"
+                    *ngIf="targetForm.showError('password', formDir, 'required')"
+                    i18n>This field is required.</span>
+
+              <span class="help-block"
+                    *ngIf="targetForm.showError('password', formDir, 'pattern')"
+                    i18n>Passwords must have a length of 12 to 16 characters
+                and can only contain letters, '@', '-', '_' or '/'.</span>
+            </div>
+          </div>
+
+          <!-- Target mutual_user -->
+          <div class="form-group"
+               [ngClass]="{'has-error': targetForm.showError('mutual_user', formDir)}">
+            <label class="control-label col-sm-3"
+                   for="target_mutual_user">
+              <ng-container i18n>Mutual User</ng-container>
+            </label>
+            <div class="col-sm-9">
+              <input class="form-control"
+                     type="text"
+                     id="target_mutual_user"
+                     name="target_mutual_user"
+                     formControlName="mutual_user" />
+
+              <span class="help-block"
+                    *ngIf="targetForm.showError('mutual_user', formDir, 'required')"
+                    i18n>This field is required.</span>
+
+              <span class="help-block"
+                    *ngIf="targetForm.showError('mutual_user', formDir, 'pattern')"
+                    i18n>Usernames must have a length of 8 to 64 characters and
+                can only contain letters, '.', '@', '-', '_' or ':'.</span>
+            </div>
+          </div>
+
+          <!-- Target mutual_password -->
+          <div class="form-group"
+               [ngClass]="{'has-error': targetForm.showError('mutual_password', formDir)}">
+            <label class="control-label col-sm-3"
+                   for="target_mutual_password">
+              <ng-container i18n>Mutual Password</ng-container>
+            </label>
+            <div class="col-sm-9">
+              <div class="input-group">
+                <input class="form-control"
+                       type="password"
+                       autocomplete="new-password"
+                       id="target_mutual_password"
+                       name="target_mutual_password"
+                       formControlName="mutual_password" />
+
+                <span class="input-group-btn">
+                  <button type="button"
+                          class="btn btn-default"
+                          cdPasswordButton="target_mutual_password">
+                  </button>
+                  <button type="button"
+                          class="btn btn-default"
+                          cdCopy2ClipboardButton="target_mutual_password">
+                  </button>
+                </span>
+              </div>
+
+              <span class="help-block"
+                    *ngIf="targetForm.showError('mutual_password', formDir, 'required')"
+                    i18n>This field is required.</span>
+
+              <span class="help-block"
+                    *ngIf="targetForm.showError('mutual_password', formDir, 'pattern')"
+                    i18n>Passwords must have a length of 12 to 16 characters
+                and can only contain letters, '@', '-', '_' or '/'.</span>
+            </div>
+          </div>
+
+        </div>
+
         <!-- Initiators -->
         <div class="form-group"
              *ngIf="targetForm.getValue('acl_enabled')">
index c4eb15dd40b8332c692fc09a6d82492c2dee168c..320afbd0af318589b0f005401654898962f819fa 100644 (file)
@@ -72,6 +72,10 @@ describe('IscsiTargetFormComponent', () => {
     { name: 'node2', ip_addresses: ['192.168.100.202'] }
   ];
 
+  const VERSION = {
+    ceph_iscsi_config_version: 11
+  };
+
   const RBD_LIST = [
     { status: 0, value: [], pool_name: 'ganesha' },
     {
@@ -152,6 +156,7 @@ describe('IscsiTargetFormComponent', () => {
 
     httpTesting.expectOne('ui-api/iscsi/settings').flush(SETTINGS);
     httpTesting.expectOne('ui-api/iscsi/portals').flush(PORTALS);
+    httpTesting.expectOne('ui-api/iscsi/version').flush(VERSION);
     httpTesting.expectOne('api/summary').flush({});
     httpTesting.expectOne('api/block/image').flush(RBD_LIST);
     httpTesting.expectOne('api/iscsi/target').flush(LIST_TARGET);
@@ -183,6 +188,12 @@ describe('IscsiTargetFormComponent', () => {
       groups: [],
       initiators: [],
       acl_enabled: false,
+      auth: {
+        password: '',
+        user: '',
+        mutual_password: '',
+        mutual_user: ''
+      },
       portals: [],
       target_controls: {},
       target_iqn: component.targetForm.value.target_iqn
@@ -386,7 +397,13 @@ describe('IscsiTargetFormComponent', () => {
         ],
         target_controls: {},
         target_iqn: component.target_iqn,
-        acl_enabled: true
+        acl_enabled: true,
+        auth: {
+          password: '',
+          user: '',
+          mutual_password: '',
+          mutual_user: ''
+        }
       });
     });
 
@@ -417,7 +434,13 @@ describe('IscsiTargetFormComponent', () => {
         ],
         target_controls: {},
         target_iqn: component.targetForm.value.target_iqn,
-        acl_enabled: true
+        acl_enabled: true,
+        auth: {
+          password: '',
+          user: '',
+          mutual_password: '',
+          mutual_user: ''
+        }
       });
     });
 
@@ -432,6 +455,12 @@ describe('IscsiTargetFormComponent', () => {
         disks: [{ backstore: 'backstore:1', controls: {}, image: 'disk_2', pool: 'rbd' }],
         groups: [],
         acl_enabled: false,
+        auth: {
+          password: '',
+          user: '',
+          mutual_password: '',
+          mutual_user: ''
+        },
         portals: [
           { host: 'node1', ip: '192.168.100.201' },
           { host: 'node2', ip: '192.168.100.202' }
index 6bbda1cd744d3d2ab0e9c73f20ec693e8802a401..d206010f8070090b97be51f0439b15d840fb4d35 100644 (file)
@@ -25,6 +25,7 @@ import { IscsiTargetIqnSettingsModalComponent } from '../iscsi-target-iqn-settin
   styleUrls: ['./iscsi-target-form.component.scss']
 })
 export class IscsiTargetFormComponent implements OnInit {
+  cephIscsiConfigVersion: number;
   targetForm: CdFormGroup;
   modalRef: BsModalRef;
   minimum_gateways = 1;
@@ -100,7 +101,8 @@ export class IscsiTargetFormComponent implements OnInit {
       this.iscsiService.listTargets(),
       this.rbdService.list(),
       this.iscsiService.portals(),
-      this.iscsiService.settings()
+      this.iscsiService.settings(),
+      this.iscsiService.version()
     ];
 
     if (this.router.url.startsWith('/block/iscsi/targets/edit')) {
@@ -160,11 +162,14 @@ export class IscsiTargetFormComponent implements OnInit {
       });
       this.portalsSelections = [...portals];
 
+      // iscsiService.version()
+      this.cephIscsiConfigVersion = data[4]['ceph_iscsi_config_version'];
+
       this.createForm();
 
       // iscsiService.getTarget()
-      if (data[4]) {
-        this.resolveModel(data[4]);
+      if (data[5]) {
+        this.resolveModel(data[5]);
       }
     });
   }
@@ -188,6 +193,17 @@ export class IscsiTargetFormComponent implements OnInit {
       groups: new FormArray([]),
       acl_enabled: new FormControl(false)
     });
+    // Target level authentication was introduced in ceph-iscsi config v11
+    if (this.cephIscsiConfigVersion > 10) {
+      const authFormGroup = new CdFormGroup({
+        user: new FormControl(''),
+        password: new FormControl(''),
+        mutual_user: new FormControl(''),
+        mutual_password: new FormControl('')
+      });
+      this.setAuthValidator(authFormGroup);
+      this.targetForm.addControl('auth', authFormGroup);
+    }
   }
 
   resolveModel(res) {
@@ -196,7 +212,12 @@ export class IscsiTargetFormComponent implements OnInit {
       target_controls: res.target_controls,
       acl_enabled: res.acl_enabled
     });
-
+    // Target level authentication was introduced in ceph-iscsi config v11
+    if (this.cephIscsiConfigVersion > 10) {
+      this.targetForm.patchValue({
+        auth: res.auth
+      });
+    }
     const portals = [];
     _.forEach(res.portals, (portal) => {
       const id = `${portal.host}:${portal.ip}`;
@@ -369,6 +390,25 @@ export class IscsiTargetFormComponent implements OnInit {
       cdIsInGroup: new FormControl(false)
     });
 
+    this.setAuthValidator(fg);
+
+    this.initiators.push(fg);
+
+    _.forEach(this.groupMembersSelections, (selections, i) => {
+      selections.push(new SelectOption(false, '', ''));
+      this.groupMembersSelections[i] = [...selections];
+    });
+
+    const disks = _.map(
+      this.targetForm.getValue('disks'),
+      (disk) => new SelectOption(false, disk, '')
+    );
+    this.imagesInitiatorSelections.push(disks);
+
+    return fg;
+  }
+
+  setAuthValidator(fg: CdFormGroup) {
     CdValidators.validateIf(
       fg.get('user'),
       () => fg.getValue('password') || fg.getValue('mutual_user') || fg.getValue('mutual_password'),
@@ -400,21 +440,6 @@ export class IscsiTargetFormComponent implements OnInit {
       [Validators.pattern(this.PASSWORD_REGEX)],
       [fg.get('user'), fg.get('password'), fg.get('mutual_user')]
     );
-
-    this.initiators.push(fg);
-
-    _.forEach(this.groupMembersSelections, (selections, i) => {
-      selections.push(new SelectOption(false, '', ''));
-      this.groupMembersSelections[i] = [...selections];
-    });
-
-    const disks = _.map(
-      this.targetForm.getValue('disks'),
-      (disk) => new SelectOption(false, disk, '')
-    );
-    this.imagesInitiatorSelections.push(disks);
-
-    return fg;
   }
 
   removeInitiator(index) {
@@ -567,6 +592,30 @@ export class IscsiTargetFormComponent implements OnInit {
       groups: []
     };
 
+    // Target level authentication was introduced in ceph-iscsi config v11
+    if (this.cephIscsiConfigVersion > 10) {
+      const targetAuth: CdFormGroup = this.targetForm.get('auth') as CdFormGroup;
+      if (!targetAuth.getValue('user')) {
+        targetAuth.get('user').setValue('');
+      }
+      if (!targetAuth.getValue('password')) {
+        targetAuth.get('password').setValue('');
+      }
+      if (!targetAuth.getValue('mutual_user')) {
+        targetAuth.get('mutual_user').setValue('');
+      }
+      if (!targetAuth.getValue('mutual_password')) {
+        targetAuth.get('mutual_password').setValue('');
+      }
+      const acl_enabled = this.targetForm.getValue('acl_enabled');
+      request['auth'] = {
+        user: acl_enabled ? '' : targetAuth.getValue('user'),
+        password: acl_enabled ? '' : targetAuth.getValue('password'),
+        mutual_user: acl_enabled ? '' : targetAuth.getValue('mutual_user'),
+        mutual_password: acl_enabled ? '' : targetAuth.getValue('mutual_password')
+      };
+    }
+
     // Disks
     formValue.disks.forEach((disk) => {
       const imageSplit = disk.split('/');
index 084182df963ca801867a54502b0e0ca6a772ade3..bcd841d9bdc0109c2f04fac971e60cdd73d3ffc2 100644 (file)
@@ -42,6 +42,7 @@
 
   <cd-iscsi-target-details cdTableDetail
                            *ngIf="selection.hasSingleSelection"
+                           [cephIscsiConfigVersion]="cephIscsiConfigVersion"
                            [selection]="selection"
                            [settings]="settings"></cd-iscsi-target-details>
 </cd-table>
index 2f93c86a63ea2bb5f3ca8220939a1eec3ae958e3..26872c5e38aba473bd643b139b1a41db07a37a66 100644 (file)
@@ -57,6 +57,7 @@ describe('IscsiTargetListComponent', () => {
     summaryService['summaryData$'] = summaryService['summaryDataSource'].asObservable();
 
     spyOn(iscsiService, 'status').and.returnValue(of({ available: true }));
+    spyOn(iscsiService, 'version').and.returnValue(of({ ceph_iscsi_config_version: 11 }));
   });
 
   it('should create', () => {
index c1f66967f9b56c7573c386927bfcfd4fb3075693..edc57bfa0f02f88ca5381266e8135be626a8a2d2 100644 (file)
@@ -36,6 +36,7 @@ export class IscsiTargetListComponent implements OnInit, OnDestroy {
   modalRef: BsModalRef;
   permissions: Permissions;
   selection = new CdTableSelection();
+  cephIscsiConfigVersion: number;
   settings: any;
   status: string;
   summaryDataSubscription: Subscription;
@@ -113,15 +114,18 @@ export class IscsiTargetListComponent implements OnInit, OnDestroy {
       this.available = result.available;
 
       if (result.available) {
-        this.taskListService.init(
-          () => this.iscsiService.listTargets(),
-          (resp) => this.prepareResponse(resp),
-          (targets) => (this.targets = targets),
-          () => this.onFetchError(),
-          this.taskFilter,
-          this.itemFilter,
-          this.builders
-        );
+        this.iscsiService.version().subscribe((res: any) => {
+          this.cephIscsiConfigVersion = res['ceph_iscsi_config_version'];
+          this.taskListService.init(
+            () => this.iscsiService.listTargets(),
+            (resp) => this.prepareResponse(resp),
+            (targets) => (this.targets = targets),
+            () => this.onFetchError(),
+            this.taskFilter,
+            this.itemFilter,
+            this.builders
+          );
+        });
 
         this.iscsiService.settings().subscribe((settings: any) => {
           this.settings = settings;
index 0c1ec456552420ba19a69cf56338e92ccdb45ef4..ff2794bf90fdc7e5ac72d2f4bcea44926ada28a6 100644 (file)
@@ -82,6 +82,10 @@ export class IscsiService {
     return this.http.get(`ui-api/iscsi/settings`);
   }
 
+  version() {
+    return this.http.get(`ui-api/iscsi/version`);
+  }
+
   portals() {
     return this.http.get(`ui-api/iscsi/portals`);
   }
index be2ca8a34d2bc09ece6617d5e02cc20c4e8a3213..593622608012610ff7936ab3f0525ca7a59f87db 100644 (file)
@@ -209,12 +209,24 @@ class IscsiClient(RestClient):
         })
 
     @RestClient.api_put('/api/targetauth/{target_iqn}')
-    def update_targetauth(self, target_iqn, action, request=None):
-        logger.debug("iSCSI[%s] Updating targetauth: %s/%s", self.gateway_name, target_iqn, action)
+    def update_targetacl(self, target_iqn, action, request=None):
+        logger.debug("iSCSI[%s] Updating targetacl: %s/%s", self.gateway_name, target_iqn, action)
         return request({
             'action': action
         })
 
+    @RestClient.api_put('/api/targetauth/{target_iqn}')
+    def update_targetauth(self, target_iqn, user, password, mutual_user, mutual_password,
+                          request=None):
+        logger.debug("iSCSI[%s] Updating targetauth: %s/%s/%s/%s/%s", self.gateway_name,
+                     target_iqn, user, password, mutual_user, mutual_password)
+        return request({
+            'username': user,
+            'password': password,
+            'mutual_username': mutual_user,
+            'mutual_password': mutual_password
+        })
+
     @RestClient.api_get('/api/targetinfo/{target_iqn}')
     def get_targetinfo(self, target_iqn, request=None):
         # pylint: disable=unused-argument
index 47649f81a42efca43b89cc2fe56ec1c523060561..c4e0d6b439bc8317052020f902bb45586574f2bf 100644 (file)
@@ -403,6 +403,11 @@ iscsi_target_request = {
         }
     ],
     "acl_enabled": True,
+    "auth": {
+        "password": "",
+        "user": "",
+        "mutual_password": "",
+        "mutual_user": ""},
     "target_controls": {},
     "groups": [
         {
@@ -459,6 +464,11 @@ iscsi_target_response = {
         }
     ],
     "acl_enabled": True,
+    "auth": {
+        "password": "",
+        "user": "",
+        "mutual_password": "",
+        "mutual_user": ""},
     'groups': [
         {
             'group_id': 'mygroup',
@@ -495,7 +505,7 @@ class IscsiClientMock(object):
             "gateways": {},
             "targets": {},
             "updated": "",
-            "version": 9
+            "version": 11
         }
 
     @classmethod
@@ -557,6 +567,14 @@ class IscsiClientMock(object):
         self.config['targets'][target_iqn] = {
             "clients": {},
             "acl_enabled": True,
+            "auth": {
+                "username": "",
+                "password": "",
+                "password_encryption_enabled": False,
+                "mutual_username": "",
+                "mutual_password": "",
+                "mutual_password_encryption_enabled": False
+            },
             "controls": target_controls,
             "created": "2019/01/17 09:22:34",
             "disks": [],
@@ -685,9 +703,16 @@ class IscsiClientMock(object):
         self.config['discovery_auth']['mutual_username'] = mutual_user
         self.config['discovery_auth']['mutual_password'] = mutual_password
 
-    def update_targetauth(self, target_iqn, action):
+    def update_targetacl(self, target_iqn, action):
         self.config['targets'][target_iqn]['acl_enabled'] = (action == 'enable_acl')
 
+    def update_targetauth(self, target_iqn, user, password, mutual_user, mutual_password):
+        target_config = self.config['targets'][target_iqn]
+        target_config['auth']['username'] = user
+        target_config['auth']['password'] = password
+        target_config['auth']['mutual_username'] = mutual_user
+        target_config['auth']['mutual_password'] = mutual_password
+
     def get_targetinfo(self, target_iqn):
         # pylint: disable=unused-argument
         return {