]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: iSCSI: Limit members to 1 group 27760/head
authorTiago Melo <tmelo@suse.com>
Mon, 15 Apr 2019 15:20:52 +0000 (15:20 +0000)
committerRicardo Marques <rimarques@suse.com>
Wed, 24 Apr 2019 16:02:08 +0000 (17:02 +0100)
Fixes: http://tracker.ceph.com/issues/39036
Signed-off-by: Tiago Melo <tmelo@suse.com>
(cherry picked from commit fb1cd2571d7f4f025db0ff6402629bd5f200672d)

src/pybind/mgr/dashboard/controllers/iscsi.py
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/tests/test_iscsi.py

index f8b0e3063b5ef93aae7634a1ba98bba2f45a210e..71926960667797a32c8b1da0e39881ec34c8c482 100644 (file)
@@ -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:
index e3134c215f790dc1f11e035e75e4e623ecd6a8f2..65e5764c7dd5e73d9a98c18e128bb5ac314ee986 100644 (file)
@@ -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', () => {
index 3d502663ca5a70320fdd35c341b6848ca60d2194..04016d78838674fa3e458c31fa408fe4929813e6 100644 (file)
@@ -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) {
index 779f1ca22bd16766898418533f8af4c99c180c77..300052960b94d06d913d54e9f62e0883e0956cb8 100644 (file)
@@ -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)