- **cluster-manager**: allows full permissions for the *hosts*, *osd*,
*monitor*, *manager*, and *config-opt* scopes.
- **pool-manager**: allows full permissions for the *pool* scope.
-- **cephfs-manager**: allows full permissions for the *cephfs* scope.
+- **cephfs-manager**: allows full permissions for the *cephfs* scope, and
+ *read* permission for the *hosts* and *pool* scopes.
The list of available roles can be retrieved with the following command:
import { ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
-import { of } from 'rxjs';
+import { of, throwError } from 'rxjs';
+import { PoolService } from '~/app/shared/api/pool.service';
+import { CephfsService } from '~/app/shared/api/cephfs.service';
import {
CheckboxModule,
ComboBoxModule,
let fixture: ComponentFixture<CephfsVolumeFormComponent>;
let formHelper: FormHelper;
let orchService: OrchestratorService;
+ let poolService: PoolService;
+ let cephfsService: CephfsService;
configureTestBed({
imports: [
component = fixture.componentInstance;
formHelper = new FormHelper(component.form);
orchService = TestBed.inject(OrchestratorService);
+ poolService = TestBed.inject(PoolService);
+ cephfsService = TestBed.inject(CephfsService);
spyOn(orchService, 'status').and.returnValue(of({ available: true }));
fixture.detectChanges();
});
expect(submitButton.nativeElement.disabled).toBeFalsy();
});
});
+
+ describe('pool list error handling when creating', () => {
+ beforeEach(() => {
+ component.editing = false;
+ spyOn(cephfsService, 'getUsedPools').and.returnValue(of([]));
+ });
+
+ it('should treat a 403 on pool list as an empty list', fakeAsync(() => {
+ spyOn(poolService, 'getList').and.returnValue(throwError({ status: 403 }));
+ component.ngOnInit();
+ tick();
+ expect(component.pools).toEqual([]);
+ }));
+
+ it('should propagate non-403 errors from pool list', fakeAsync(() => {
+ const err = { status: 500 };
+ spyOn(poolService, 'getList').and.returnValue(throwError(err));
+ let errorHandled = false;
+ const origSetErrors = component.form.setErrors.bind(component.form);
+ spyOn(component.form, 'setErrors').and.callFake((errors: any) => {
+ if (errors?.cdSubmitButton) errorHandled = true;
+ origSetErrors(errors);
+ });
+ component.ngOnInit();
+ tick();
+ expect(errorHandled).toBeTruthy();
+ }));
+ });
});
import _ from 'lodash';
import { NgbNav, NgbTooltip, NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
-import { forkJoin, Observable, Subject } from 'rxjs';
-import { map } from 'rxjs/operators';
+import { forkJoin, Observable, of, Subject, throwError } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
import { CephfsService } from '~/app/shared/api/cephfs.service';
import { HostService } from '~/app/shared/api/host.service';
} else {
forkJoin({
usedPools: this.cephfsService.getUsedPools(),
- pools: this.poolService.getList()
- }).subscribe(({ usedPools, pools }) => {
- // filtering pools if
- // * pool is labelled with cephfs
- // * its not already used by cephfs
- // * its not erasure coded
- // * and only if its empty
- const filteredPools = Object.values(pools).filter(
- (pool: Pool) =>
- this.cephfsService.isCephFsPool(pool) &&
- !usedPools.includes(pool.pool) &&
- pool.type !== 'erasure' &&
- pool.stats.bytes_used.latest === 0
- );
- if (filteredPools.length < 2) this.form.get('customPools').disable();
- this.pools = filteredPools;
- this.metadatPools = this.dataPools = this.pools;
+ pools: this.poolService
+ .getList()
+ .pipe(catchError((err) => (err.status === 403 ? of([]) : throwError(() => err))))
+ }).subscribe({
+ next: ({ usedPools, pools }) => {
+ // filtering pools if
+ // * pool is labelled with cephfs
+ // * its not already used by cephfs
+ // * its not erasure coded
+ // * and only if its empty
+ const filteredPools = Object.values(pools).filter(
+ (pool: Pool) =>
+ this.cephfsService.isCephFsPool(pool) &&
+ !usedPools.includes(pool.pool) &&
+ pool.type !== 'erasure' &&
+ pool.stats.bytes_used.latest === 0
+ );
+ if (filteredPools.length < 2) this.form.get('customPools').disable();
+ this.pools = filteredPools;
+ this.metadatPools = this.dataPools = this.pools;
+ },
+ error: () => {
+ this.form.setErrors({ cdSubmitButton: true });
+ }
});
this.hostsAndLabels$ = forkJoin({
- hosts: this.hostService.getAllHosts(),
- labels: this.hostService.getLabels()
+ hosts: this.hostService
+ .getAllHosts()
+ .pipe(catchError((err) => (err.status === 403 ? of([]) : throwError(() => err)))),
+ labels: this.hostService
+ .getLabels()
+ .pipe(catchError((err) => (err.status === 403 ? of([]) : throwError(() => err))))
}).pipe(
map(({ hosts, labels }) => ({
hosts: hosts.map((host: Host) => ({ content: host['hostname'] })),
CEPHFS_MGR_ROLE = Role(
'cephfs-manager', 'allows full permissions for the cephfs scope', {
Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+ Scope.HOSTS: [_P.READ],
+ Scope.POOL: [_P.READ],
Scope.GRAFANA: [_P.READ],
Scope.PROMETHEUS: [_P.READ]
})
from .. import mgr
from ..security import Permission, Scope
-from ..services.access_control import SYSTEM_ROLES, AccessControlDB, \
- PasswordPolicy, load_access_control_db, password_hash
+from ..services.access_control import CEPHFS_MGR_ROLE, SYSTEM_ROLES, \
+ AccessControlDB, PasswordPolicy, load_access_control_db, password_hash
from ..settings import Settings
from ..tests import CLICommandTestMixin, CmdException
role = self.exec_cmd('ac-role-show', rolename='block-manager')
self.assertEqual(role['scopes_permissions'][Scope.HOSTS], [Permission.READ])
+ def test_cephfs_mgr_role_scopes(self):
+ role = self.exec_cmd('ac-role-show', rolename='cephfs-manager')
+ self.assertEqual(role['name'], CEPHFS_MGR_ROLE.name)
+ scopes_perms = role['scopes_permissions']
+ self.assertIn(Scope.HOSTS, scopes_perms)
+ self.assertEqual(scopes_perms[Scope.HOSTS], [Permission.READ])
+ self.assertIn(Scope.POOL, scopes_perms)
+ self.assertEqual(scopes_perms[Scope.POOL], [Permission.READ])
+
def test_delete_system_role(self):
with self.assertRaises(CmdException) as ctx:
self.exec_cmd('ac-role-delete', rolename='administrator')