]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Validate iSCSI images features
authorRicardo Marques <rimarques@suse.com>
Fri, 22 Mar 2019 18:53:29 +0000 (18:53 +0000)
committerTatjana Dehler <tdehler@suse.com>
Thu, 4 Apr 2019 09:21:41 +0000 (11:21 +0200)
Will also filter the list of available images.

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

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 5342a4463b59244ff52b250e57c9f7344c685d9e..e684bef61aaee37653c5be495e02f6ee9fa91d6d 100644 (file)
@@ -350,14 +350,31 @@ class IscsiTarget(RESTController):
         for disk in disks:
             pool = disk['pool']
             image = disk['image']
-            IscsiTarget._validate_image_exists(pool, image)
+            backstore = disk['backstore']
+            required_rbd_features = settings['required_rbd_features'][backstore]
+            supported_rbd_features = settings['supported_rbd_features'][backstore]
+            IscsiTarget._validate_image(pool, image, backstore, required_rbd_features,
+                                        supported_rbd_features)
 
     @staticmethod
-    def _validate_image_exists(pool, image):
+    def _validate_image(pool, image, backstore, required_rbd_features, supported_rbd_features):
         try:
             ioctx = mgr.rados.open_ioctx(pool)
             try:
-                rbd.Image(ioctx, image)
+                with rbd.Image(ioctx, image) as img:
+                    if img.features() & required_rbd_features != required_rbd_features:
+                        raise DashboardException(msg='Image {} cannot be exported using {} '
+                                                     'backstore because required features are '
+                                                     'missing'.format(image, backstore),
+                                                 code='image_missing_required_features',
+                                                 component='iscsi')
+                    if img.features() & supported_rbd_features != img.features():
+                        raise DashboardException(msg='Image {} cannot be exported using {} '
+                                                     'backstore because it contains unsupported '
+                                                     'features'.format(image, backstore),
+                                                 code='image_contains_unsupported_features',
+                                                 component='iscsi')
+
             except rbd.ImageNotFound:
                 raise DashboardException(msg='Image {} does not exist'.format(image),
                                          code='image_does_not_exist',
index 5ee56d2b4f8e39e165aaa24ff3120e0217f12db9..29fc65aa22ea2540b1df2b965e18ebd166bacdc0 100644 (file)
@@ -33,6 +33,14 @@ describe('IscsiTargetFormComponent', () => {
       dataout_timeout: 20,
       immediate_data: 'Yes'
     },
+    required_rbd_features: {
+      'backstore:1': 0,
+      'backstore:2': 0
+    },
+    supported_rbd_features: {
+      'backstore:1': 61,
+      'backstore:2': 61
+    },
     backstores: ['backstore:1', 'backstore:2'],
     default_backstore: 'backstore:1'
   };
@@ -41,7 +49,7 @@ describe('IscsiTargetFormComponent', () => {
     {
       target_iqn: 'iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw',
       portals: [{ host: 'node1', ip: '192.168.100.201' }],
-      disks: [{ pool: 'rbd', image: 'disk_1', controls: {} }],
+      disks: [{ pool: 'rbd', image: 'disk_1', controls: {}, backstore: 'backstore:1' }],
       clients: [
         {
           client_iqn: 'iqn.1994-05.com.redhat:rh7-client',
@@ -155,7 +163,7 @@ describe('IscsiTargetFormComponent', () => {
   });
 
   it('should only show images not used in other targets', () => {
-    expect(component.imagesAll).toEqual(['rbd/disk_2']);
+    expect(component.imagesAll).toEqual([RBD_LIST[1]['value'][1]]);
     expect(component.imagesSelections).toEqual([
       { description: '', name: 'rbd/disk_2', selected: false }
     ]);
@@ -183,9 +191,9 @@ describe('IscsiTargetFormComponent', () => {
 
   it('should prepare data when selecting an image', () => {
     expect(component.imagesSettings).toEqual({});
-    component.onImageSelection({ option: { name: 'rbd/disk_1', selected: true } });
+    component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
     expect(component.imagesSettings).toEqual({
-      'rbd/disk_1': {
+      'rbd/disk_2': {
         backstore: 'backstore:1',
         'backstore:1': {}
       }
@@ -193,24 +201,24 @@ describe('IscsiTargetFormComponent', () => {
   });
 
   it('should clean data when removing an image', () => {
-    component.onImageSelection({ option: { name: 'rbd/disk_1', selected: true } });
+    component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
     component.addGroup();
     component.groups.controls[0].patchValue({
       group_id: 'foo',
-      disks: ['rbd/disk_1']
+      disks: ['rbd/disk_2']
     });
 
     expect(component.groups.controls[0].value).toEqual({
-      disks: ['rbd/disk_1'],
+      disks: ['rbd/disk_2'],
       group_id: 'foo',
       members: []
     });
 
-    component.onImageSelection({ option: { name: 'rbd/disk_1', selected: false } });
+    component.onImageSelection({ option: { name: 'rbd/disk_2', selected: false } });
 
     expect(component.groups.controls[0].value).toEqual({ disks: [], group_id: 'foo', members: [] });
     expect(component.imagesSettings).toEqual({
-      'rbd/disk_1': {
+      'rbd/disk_2': {
         backstore: 'backstore:1',
         'backstore:1': {}
       }
@@ -219,9 +227,9 @@ describe('IscsiTargetFormComponent', () => {
 
   describe('should test initiators', () => {
     beforeEach(() => {
-      component.targetForm.patchValue({ disks: ['rbd/disk_1'], acl_enabled: true });
+      component.targetForm.patchValue({ disks: ['rbd/disk_2'], acl_enabled: true });
       component.addGroup().patchValue({ name: 'group_1' });
-      component.onImageSelection({ option: { name: 'rbd/disk_1', selected: true } });
+      component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
 
       component.addInitiator();
       component.initiators.controls[0].patchValue({
@@ -239,7 +247,7 @@ describe('IscsiTargetFormComponent', () => {
         luns: []
       });
       expect(component.imagesInitiatorSelections).toEqual([
-        [{ description: '', name: 'rbd/disk_1', selected: false }]
+        [{ description: '', name: 'rbd/disk_2', selected: false }]
       ]);
       expect(component.groupMembersSelections).toEqual([
         [{ description: '', name: 'iqn.initiator', selected: false }]
@@ -286,13 +294,13 @@ describe('IscsiTargetFormComponent', () => {
 
     it('should remove images in the initiator when added in a group', () => {
       component.initiators.controls[0].patchValue({
-        luns: ['rbd/disk_1']
+        luns: ['rbd/disk_2']
       });
       expect(component.initiators.controls[0].value).toEqual({
         auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
         cdIsInGroup: false,
         client_iqn: 'iqn.initiator',
-        luns: ['rbd/disk_1']
+        luns: ['rbd/disk_2']
       });
 
       component.addGroup();
@@ -318,8 +326,8 @@ describe('IscsiTargetFormComponent', () => {
 
   describe('should submit request', () => {
     beforeEach(() => {
-      component.targetForm.patchValue({ disks: ['rbd/disk_1'], acl_enabled: true });
-      component.onImageSelection({ option: { name: 'rbd/disk_1', selected: true } });
+      component.targetForm.patchValue({ disks: ['rbd/disk_2'], acl_enabled: true });
+      component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
       component.portals.setValue(['node1:192.168.100.201', 'node2:192.168.100.202']);
       component.addInitiator().patchValue({
         client_iqn: 'iqn.initiator'
@@ -327,7 +335,7 @@ describe('IscsiTargetFormComponent', () => {
       component.addGroup().patchValue({
         group_id: 'foo',
         members: ['iqn.initiator'],
-        disks: ['rbd/disk_1']
+        disks: ['rbd/disk_2']
       });
     });
 
@@ -349,9 +357,9 @@ describe('IscsiTargetFormComponent', () => {
             luns: []
           }
         ],
-        disks: [{ backstore: 'backstore:1', controls: {}, image: 'disk_1', pool: 'rbd' }],
+        disks: [{ backstore: 'backstore:1', controls: {}, image: 'disk_2', pool: 'rbd' }],
         groups: [
-          { disks: [{ image: 'disk_1', pool: 'rbd' }], group_id: 'foo', members: ['iqn.initiator'] }
+          { disks: [{ image: 'disk_2', pool: 'rbd' }], group_id: 'foo', members: ['iqn.initiator'] }
         ],
         new_target_iqn: component.targetForm.value.target_iqn,
         portals: [
@@ -378,10 +386,10 @@ describe('IscsiTargetFormComponent', () => {
             luns: []
           }
         ],
-        disks: [{ backstore: 'backstore:1', controls: {}, image: 'disk_1', pool: 'rbd' }],
+        disks: [{ backstore: 'backstore:1', controls: {}, image: 'disk_2', pool: 'rbd' }],
         groups: [
           {
-            disks: [{ image: 'disk_1', pool: 'rbd' }],
+            disks: [{ image: 'disk_2', pool: 'rbd' }],
             group_id: 'foo',
             members: ['iqn.initiator']
           }
@@ -404,7 +412,7 @@ describe('IscsiTargetFormComponent', () => {
       expect(req.request.method).toBe('POST');
       expect(req.request.body).toEqual({
         clients: [],
-        disks: [{ backstore: 'backstore:1', controls: {}, image: 'disk_1', pool: 'rbd' }],
+        disks: [{ backstore: 'backstore:1', controls: {}, image: 'disk_2', pool: 'rbd' }],
         groups: [],
         acl_enabled: false,
         portals: [
index 99127652b7c0171238e2e5270bb6150b1439a778..bd68a3399775b86b5f0e9ef65a2ed6baab8d1db6 100644 (file)
@@ -31,6 +31,8 @@ export class IscsiTargetFormComponent implements OnInit {
   disk_default_controls: any;
   backstores: string[];
   default_backstore: string;
+  supported_rbd_features: any;
+  required_rbd_features: any;
 
   isEdit = false;
   target_iqn: string;
@@ -109,14 +111,34 @@ export class IscsiTargetFormComponent implements OnInit {
         .map((image) => `${image.pool}/${image.image}`)
         .value();
 
+      // iscsiService.settings()
+      this.minimum_gateways = data[3].config.minimum_gateways;
+      this.target_default_controls = data[3].target_default_controls;
+      this.disk_default_controls = data[3].disk_default_controls;
+      this.backstores = data[3].backstores;
+      this.default_backstore = data[3].default_backstore;
+      this.supported_rbd_features = data[3].supported_rbd_features;
+      this.required_rbd_features = data[3].required_rbd_features;
+
       // rbdService.list()
       this.imagesAll = _(data[1])
         .flatMap((pool) => pool.value)
-        .map((image) => `${image.pool_name}/${image.name}`)
-        .filter((image) => usedImages.indexOf(image) === -1)
+        .filter((image) => {
+          const imageId = `${image.pool_name}/${image.name}`;
+          if (usedImages.indexOf(imageId) !== -1) {
+            return false;
+          }
+          const validBackstores = this.getValidBackstores(image);
+          if (validBackstores.length === 0) {
+            return false;
+          }
+          return true;
+        })
         .value();
 
-      this.imagesSelections = this.imagesAll.map((image) => new SelectOption(false, image, ''));
+      this.imagesSelections = this.imagesAll.map(
+        (image) => new SelectOption(false, `${image.pool_name}/${image.name}`, '')
+      );
 
       // iscsiService.portals()
       const portals: SelectOption[] = [];
@@ -127,13 +149,6 @@ export class IscsiTargetFormComponent implements OnInit {
       });
       this.portalsSelections = [...portals];
 
-      // iscsiService.settings()
-      this.minimum_gateways = data[3].config.minimum_gateways;
-      this.target_default_controls = data[3].target_default_controls;
-      this.disk_default_controls = data[3].disk_default_controls;
-      this.backstores = data[3].backstores;
-      this.default_backstore = data[3].default_backstore;
-
       this.createForm();
 
       // iscsiService.getTarget()
@@ -273,15 +288,31 @@ export class IscsiTargetFormComponent implements OnInit {
     });
   }
 
+  getDefaultBackstore(imageId) {
+    let result = this.default_backstore;
+    const image = this.getImageById(imageId);
+    if (!this.validFeatures(image, this.default_backstore)) {
+      this.backstores.forEach((backstore) => {
+        if (backstore !== this.default_backstore) {
+          if (this.validFeatures(image, backstore)) {
+            result = backstore;
+          }
+        }
+      });
+    }
+    return result;
+  }
+
   onImageSelection($event) {
     const option = $event.option;
 
     if (option.selected) {
       if (!this.imagesSettings[option.name]) {
+        const defaultBackstore = this.getDefaultBackstore(option.name);
         this.imagesSettings[option.name] = {
-          backstore: this.default_backstore
+          backstore: defaultBackstore
         };
-        this.imagesSettings[option.name][this.default_backstore] = {};
+        this.imagesSettings[option.name][defaultBackstore] = {};
       }
 
       _.forEach(this.imagesInitiatorSelections, (selections, i) => {
@@ -628,11 +659,30 @@ export class IscsiTargetFormComponent implements OnInit {
       imagesSettings: this.imagesSettings,
       image: image,
       disk_default_controls: this.disk_default_controls,
-      backstores: this.backstores
+      backstores: this.getValidBackstores(this.getImageById(image))
     };
 
     this.modalRef = this.modalService.show(IscsiTargetImageSettingsModalComponent, {
       initialState
     });
   }
+
+  validFeatures(image, backstore) {
+    const imageFeatures = image.features;
+    const requiredFeatures = this.required_rbd_features[backstore];
+    const supportedFeatures = this.supported_rbd_features[backstore];
+    // tslint:disable-next-line:no-bitwise
+    const validRequiredFeatures = (imageFeatures & requiredFeatures) === requiredFeatures;
+    // tslint:disable-next-line:no-bitwise
+    const validSupportedFeatures = (imageFeatures & supportedFeatures) === imageFeatures;
+    return validRequiredFeatures && validSupportedFeatures;
+  }
+
+  getImageById(imageId) {
+    return this.imagesAll.find((image) => imageId === `${image.pool_name}/${image.name}`);
+  }
+
+  getValidBackstores(image) {
+    return this.backstores.filter((backstore) => this.validFeatures(image, backstore));
+  }
 }
index c8d99eea280ab5df7456f8631dc52a0899610860..db88f32f0914cba4abb2dd81f28a5751bbec7308 100644 (file)
@@ -98,8 +98,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         self.assertStatus(200)
         self.assertJsonBody([])
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_list(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_list(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw1"
         request = copy.deepcopy(iscsi_target_request)
         request['target_iqn'] = target_iqn
@@ -111,8 +111,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['target_iqn'] = target_iqn
         self.assertJsonBody([response])
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_create(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_create(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw2"
         request = copy.deepcopy(iscsi_target_request)
         request['target_iqn'] = target_iqn
@@ -124,8 +124,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['target_iqn'] = target_iqn
         self.assertJsonBody(response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_delete(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_delete(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw3"
         request = copy.deepcopy(iscsi_target_request)
         request['target_iqn'] = target_iqn
@@ -137,8 +137,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         self.assertStatus(200)
         self.assertJsonBody([])
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_add_client(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_add_client(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw4"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -168,8 +168,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
             })
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_change_client_password(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_change_client_password(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw5"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -181,8 +181,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['clients'][0]['auth']['password'] = 'mynewiscsipassword'
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_rename_client(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_rename_client(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw6"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -194,8 +194,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['clients'][0]['client_iqn'] = 'iqn.1994-05.com.redhat:rh7-client0'
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_add_disk(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_add_disk(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw7"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -221,8 +221,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['clients'][0]['luns'].append({"image": "lun3", "pool": "rbd"})
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_change_disk_image(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_change_disk_image(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw8"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -236,8 +236,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['clients'][0]['luns'][0]['image'] = 'lun0'
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_change_disk_controls(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_change_disk_controls(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw9"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -249,8 +249,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['disks'][0]['controls'] = {"qfull_timeout": 15}
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_rename_target(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_rename_target(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw10"
         new_target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw11"
         create_request = copy.deepcopy(iscsi_target_request)
@@ -261,8 +261,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['target_iqn'] = new_target_iqn
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_rename_group(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_rename_group(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw12"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -274,8 +274,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['groups'][0]['group_id'] = 'mygroup0'
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_add_client_to_group(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_add_client_to_group(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw13"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -307,8 +307,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['groups'][0]['members'].append('iqn.1994-05.com.redhat:rh7-client3')
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_remove_client_from_group(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_remove_client_from_group(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw14"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -320,8 +320,8 @@ class IscsiTest(ControllerTestCase, CLICommandTestMixin):
         response['groups'][0]['members'].remove('iqn.1994-05.com.redhat:rh7-client2')
         self._update_iscsi_target(create_request, update_request, response)
 
-    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
-    def test_remove_groups(self, _validate_image_exists_mock):
+    @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image')
+    def test_remove_groups(self, _validate_image_mock):
         target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw15"
         create_request = copy.deepcopy(iscsi_target_request)
         create_request['target_iqn'] = target_iqn
@@ -481,6 +481,14 @@ class IscsiClientMock(object):
                 "minimum_gateways": 2
             },
             "default_backstore": "user:rbd",
+            "required_rbd_features": {
+                "rbd": 0,
+                "user:rbd": 4,
+            },
+            "supported_rbd_features": {
+                "rbd": 135,
+                "user:rbd": 61,
+            },
             "disk_default_controls": {
                 "user:rbd": {
                     "hw_max_sectors": 1024,