class RbdTest(DashboardTestCase):
- AUTH_ROLES = ['pool-manager', 'block-manager']
+ AUTH_ROLES = ['pool-manager', 'block-manager', 'cluster-manager']
@classmethod
def create_pool(cls, name, pg_num, pool_type, application='rbd'):
self.assertEqual(default_features, [
'deep-flatten', 'exclusive-lock', 'fast-diff', 'layering', 'object-map'])
+ def test_clone_format_version(self):
+ config_name = 'rbd_default_clone_format'
+ def _get_config_by_name(conf_name):
+ data = self._get('/api/cluster_conf/{}'.format(conf_name))
+ if 'value' in data:
+ return data['value']
+ return None
+
+ # with rbd_default_clone_format = auto
+ clone_format_version = self._get('/api/block/image/clone_format_version')
+ self.assertEqual(clone_format_version, 1)
+ self.assertStatus(200)
+
+ # with rbd_default_clone_format = 1
+ value = [{'section': "global", 'value': "1"}]
+ self._post('/api/cluster_conf', {
+ 'name': config_name,
+ 'value': value
+ })
+ self.wait_until_equal(
+ lambda: _get_config_by_name(config_name),
+ value,
+ timeout=60)
+ clone_format_version = self._get('/api/block/image/clone_format_version')
+ self.assertEqual(clone_format_version, 1)
+ self.assertStatus(200)
+
+ # with rbd_default_clone_format = 2
+ value = [{'section': "global", 'value': "2"}]
+ self._post('/api/cluster_conf', {
+ 'name': config_name,
+ 'value': value
+ })
+ self.wait_until_equal(
+ lambda: _get_config_by_name(config_name),
+ value,
+ timeout=60)
+ clone_format_version = self._get('/api/block/image/clone_format_version')
+ self.assertEqual(clone_format_version, 2)
+ self.assertStatus(200)
+
+ value = []
+ self._post('/api/cluster_conf', {
+ 'name': config_name,
+ 'value': value
+ })
+ self.wait_until_equal(
+ lambda: _get_config_by_name(config_name),
+ None,
+ timeout=60)
+
def test_image_with_namespace(self):
self.create_namespace('rbd', 'ns')
self.create_image('rbd', 'ns', 'test', 10240)
rbd_default_features = mgr.get('config')['rbd_default_features']
return format_bitmask(int(rbd_default_features))
+ @RESTController.Collection('GET')
+ def clone_format_version(self):
+ """Return the RBD clone format version.
+ """
+ rbd_default_clone_format = mgr.get('config')['rbd_default_clone_format']
+ if rbd_default_clone_format != 'auto':
+ return int(rbd_default_clone_format)
+ osd_map = mgr.get_osdmap().dump()
+ min_compat_client = osd_map.get('min_compat_client', '')
+ require_min_compat_client = osd_map.get('require_min_compat_client', '')
+ if max(min_compat_client, require_min_compat_client) < 'mimic':
+ return 1
+
+ return 2
+
@RbdTask('trash/move', ['{image_spec}'], 2.0)
@RESTController.Resource('POST')
@allow_empty_body
+import { RbdService } from 'app/shared/api/rbd.service';
+
import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
import { Icons } from '../../../shared/enum/icons.enum';
import { CdTableAction } from '../../../shared/models/cd-table-action';
deleteSnap: CdTableAction;
ordering: CdTableAction[];
- constructor(actionLabels: ActionLabelsI18n, featuresName: string[]) {
+ cloneFormatVersion = 1;
+
+ constructor(actionLabels: ActionLabelsI18n, featuresName: string[], rbdService: RbdService) {
+ rbdService.cloneFormatVersion().subscribe((version: number) => {
+ this.cloneFormatVersion = version;
+ });
+
this.create = {
permission: 'create',
icon: Icons.add,
return $localize`Parent image must support Layering`;
}
+ if (this.cloneFormatVersion === 1 && !selection.first().is_protected) {
+ return $localize`Snapshot must be protected in order to clone.`;
+ }
+
return false;
}
import { TaskListService } from '../../../shared/services/task-list.service';
import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component';
import { RbdTabsComponent } from '../rbd-tabs/rbd-tabs.component';
+import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
import { RbdSnapshotListComponent } from './rbd-snapshot-list.component';
import { RbdSnapshotModel } from './rbd-snapshot.model';
}
});
});
+
+ describe('clone button disable state', () => {
+ let actions: RbdSnapshotActionsModel;
+
+ beforeEach(() => {
+ fixture.detectChanges();
+ const rbdService = TestBed.inject(RbdService);
+ const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
+ actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
+ });
+
+ it('should be disabled with version 1 and protected false', () => {
+ const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
+ const disableDesc = actions.getCloneDisableDesc(selection, ['layering']);
+ expect(disableDesc).toBe('Snapshot must be protected in order to clone.');
+ });
+
+ it.each([
+ [1, true],
+ [2, true],
+ [2, false]
+ ])('should be enabled with version %d and protected %s', (version, is_protected) => {
+ actions.cloneFormatVersion = version;
+ const selection = new CdTableSelection([{ name: 'someName', is_protected: is_protected }]);
+ const disableDesc = actions.getCloneDisableDesc(selection, ['layering']);
+ expect(disableDesc).toBe(false);
+ });
+ });
});
ngOnChanges() {
const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
- const actions = new RbdSnapshotActionsModel(this.actionLabels, this.featuresName);
+ const actions = new RbdSnapshotActionsModel(
+ this.actionLabels,
+ this.featuresName,
+ this.rbdService
+ );
actions.create.click = () => this.openCreateSnapshotModal();
actions.rename.click = () => this.openEditSnapshotModal();
actions.protect.click = () => this.toggleProtection();
expect(req.request.method).toBe('GET');
});
+ it('should call cloneFormatVersion', () => {
+ service.cloneFormatVersion().subscribe();
+ const req = httpTesting.expectOne('api/block/image/clone_format_version');
+ expect(req.request.method).toBe('GET');
+ });
+
it('should call createSnapshot', () => {
service.createSnapshot(new ImageSpec('poolName', null, 'rbdName'), 'snapshotName').subscribe();
const req = httpTesting.expectOne('api/block/image/poolName%2FrbdName/snap');
return this.http.get('api/block/image/default_features');
}
+ cloneFormatVersion() {
+ return this.http.get<number>('api/block/image/clone_format_version');
+ }
+
createSnapshot(imageSpec: ImageSpec, @cdEncodeNot snapshotName: string) {
const request = {
snapshot_name: snapshotName
- jwt: []
tags:
- Rbd
+ /api/block/image/clone_format_version:
+ get:
+ description: "Return the RBD clone format version.\n "
+ parameters: []
+ responses:
+ '200':
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - Rbd
/api/block/image/default_features:
get:
parameters: []