From: Tiago Melo Date: Mon, 15 Apr 2019 15:20:52 +0000 (+0000) Subject: mgr/dashboard: iSCSI: Limit members to 1 group X-Git-Tag: v14.2.2~155^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=a8b41d2cff1c7f5ecb83cde8de33bac1ae900efd;p=ceph.git mgr/dashboard: iSCSI: Limit members to 1 group Fixes: http://tracker.ceph.com/issues/39036 Signed-off-by: Tiago Melo (cherry picked from commit fb1cd2571d7f4f025db0ff6402629bd5f200672d) --- diff --git a/src/pybind/mgr/dashboard/controllers/iscsi.py b/src/pybind/mgr/dashboard/controllers/iscsi.py index f8b0e3063b5ef..7192696066779 100644 --- a/src/pybind/mgr/dashboard/controllers/iscsi.py +++ b/src/pybind/mgr/dashboard/controllers/iscsi.py @@ -219,7 +219,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 +241,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, @@ -398,7 +398,7 @@ class IscsiTarget(RESTController): 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', @@ -435,6 +435,14 @@ class IscsiTarget(RESTController): IscsiTarget._validate_image(pool, image, backstore, required_rbd_features, supported_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): try: diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.spec.ts index e3134c215f790..65e5764c7dd5e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.spec.ts @@ -229,6 +229,7 @@ describe('IscsiTargetFormComponent', () => { beforeEach(() => { component.targetForm.patchValue({ disks: ['rbd/disk_2'], acl_enabled: true }); component.addGroup().patchValue({ name: 'group_1' }); + component.addGroup().patchValue({ name: 'group_2' }); component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } }); component.addInitiator(); @@ -250,12 +251,14 @@ describe('IscsiTargetFormComponent', () => { [{ description: '', name: 'rbd/disk_2', selected: false, enabled: true }] ]); expect(component.groupMembersSelections).toEqual([ + [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }], [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }] ]); }); it('should update data when changing an initiator name', () => { expect(component.groupMembersSelections).toEqual([ + [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }], [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }] ]); @@ -265,6 +268,7 @@ describe('IscsiTargetFormComponent', () => { component.updatedInitiatorSelector(); expect(component.groupMembersSelections).toEqual([ + [{ description: '', name: 'iqn.initiator_new', selected: false, enabled: true }], [{ description: '', name: 'iqn.initiator_new', selected: false, enabled: true }] ]); }); @@ -288,7 +292,7 @@ describe('IscsiTargetFormComponent', () => { group_id: 'foo', members: [] }); - expect(component.groupMembersSelections).toEqual([[]]); + expect(component.groupMembersSelections).toEqual([[], []]); expect(component.imagesInitiatorSelections).toEqual([]); }); @@ -322,6 +326,21 @@ describe('IscsiTargetFormComponent', () => { luns: [] }); }); + + it('should disabled the initiator when selected', () => { + expect(component.groupMembersSelections).toEqual([ + [{ description: '', enabled: true, name: 'iqn.initiator', selected: false }], + [{ description: '', enabled: true, name: 'iqn.initiator', selected: false }] + ]); + + component.groupMembersSelections[0][0].selected = true; + component.onGroupMemberSelection({ option: { name: 'iqn.initiator', selected: true } }); + + expect(component.groupMembersSelections).toEqual([ + [{ description: '', enabled: false, name: 'iqn.initiator', selected: true }], + [{ description: '', enabled: false, name: 'iqn.initiator', selected: false }] + ]); + }); }); describe('should submit request', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts index 3d502663ca5a7..04016d7883867 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts @@ -226,7 +226,6 @@ export class IscsiTargetFormComponent implements OnInit { _.forEach(res.groups, (group) => { const fg = this.addGroup(); - console.log(group); group.disks = _.map(group.disks, (disk) => `${disk.pool}/${disk.image}`); fg.patchValue(group); _.forEach(group.members, (member) => { @@ -494,7 +493,7 @@ export class IscsiTargetFormComponent implements OnInit { const initiators = _.map( this.initiators.value, - (initiator) => new SelectOption(false, initiator.client_iqn, '') + (initiator) => new SelectOption(false, initiator.client_iqn, '', !initiator.cdIsInGroup) ); this.groupMembersSelections.push(initiators); @@ -509,12 +508,20 @@ export class IscsiTargetFormComponent implements OnInit { onGroupMemberSelection($event) { const option = $event.option; - this.initiators.controls.forEach((element) => { + let initiator_index: number; + this.initiators.controls.forEach((element, index) => { if (element.value.client_iqn === option.name) { element.patchValue({ luns: [] }); element.get('cdIsInGroup').setValue(option.selected); + initiator_index = index; } }); + + // Members can only be at one group at a time, so when a member is selected + // in one group we need to disable its selection in other groups + _.forEach(this.groupMembersSelections, (group) => { + group[initiator_index].enabled = !option.selected; + }); } removeGroupInitiator(group, member_index, group_index) { diff --git a/src/pybind/mgr/dashboard/tests/test_iscsi.py b/src/pybind/mgr/dashboard/tests/test_iscsi.py index 779f1ca22bd16..300052960b94d 100644 --- a/src/pybind/mgr/dashboard/tests/test_iscsi.py +++ b/src/pybind/mgr/dashboard/tests/test_iscsi.py @@ -333,6 +333,21 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin): response['groups'] = [] self._update_iscsi_target(create_request, update_request, response) + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_add_client_to_multiple_groups(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw16" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + create_request['groups'].append(copy.deepcopy(create_request['groups'][0])) + create_request['groups'][1]['group_id'] = 'mygroup2' + self._post('/api/iscsi/target', create_request) + self.assertStatus(400) + self.assertJsonBody({ + 'detail': 'Each initiator can only be part of 1 group at a time', + 'code': 'initiator_in_multiple_groups', + 'component': 'iscsi' + }) + def _update_iscsi_target(self, create_request, update_request, response): self._post('/api/iscsi/target', create_request) self.assertStatus(201)