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',
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'
};
{
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',
});
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 }
]);
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': {}
}
});
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': {}
}
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({
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 }]
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();
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'
component.addGroup().patchValue({
group_id: 'foo',
members: ['iqn.initiator'],
- disks: ['rbd/disk_1']
+ disks: ['rbd/disk_2']
});
});
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: [
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']
}
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: [
disk_default_controls: any;
backstores: string[];
default_backstore: string;
+ supported_rbd_features: any;
+ required_rbd_features: any;
isEdit = false;
target_iqn: string;
.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[] = [];
});
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()
});
}
+ 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) => {
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));
+ }
}
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
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
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
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
})
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
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
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
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
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
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)
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
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
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
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
"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,