self.assertIn('status', data)
self.assertIn('data', data)
+ def test_cephfs_evict_client_does_not_exist(self):
+ fs_id = self.fs.get_namespace_id()
+ data = self._delete("/api/cephfs/{}/client/1234".format(fs_id))
+ self.assertStatus(404)
+
def test_cephfs_get(self):
fs_id = self.fs.get_namespace_id()
data = self._get("/api/cephfs/{}/".format(fs_id))
return self._clients(fs_id)
+ @RESTController.Resource('DELETE', path='/client/{client_id}')
+ def evict(self, fs_id, client_id):
+ fs_id = self.fs_id_to_int(fs_id)
+ client_id = self.client_id_to_int(client_id)
+
+ return self._evict(fs_id, client_id)
+
@RESTController.Resource('GET')
def mds_counters(self, fs_id):
"""
msg="Invalid cephfs ID {}".format(fs_id),
component='cephfs')
+ @staticmethod
+ def client_id_to_int(client_id):
+ try:
+ return int(client_id)
+ except ValueError:
+ raise DashboardException(code='invalid_cephfs_client_id',
+ msg="Invalid cephfs client ID {}".format(client_id),
+ component='cephfs')
+
def _get_mds_names(self, filesystem_id=None):
names = []
'data': clients
}
+ def _evict(self, fs_id, client_id):
+ clients = self._clients(fs_id)
+ if not [c for c in clients['data'] if c['id'] == client_id]:
+ raise cherrypy.HTTPError(404,
+ "Client {0} does not exist in cephfs {1}".format(client_id,
+ fs_id))
+ CephService.send_command('mds', 'client evict',
+ srv_spec='{0}:0'.format(fs_id), id=client_id)
+
class CephFSClients(object):
def __init__(self, module_inst, fscid):
<cd-table [data]="clients.data"
[columns]="clients.columns"
- (fetchData)="refresh()">
+ (fetchData)="refresh()"
+ selectionType="single"
+ (updateSelection)="updateSelection($event)">
+ <cd-table-actions class="table-actions"
+ [permission]="permission"
+ [selection]="selection"
+ [tableActions]="tableActions">
+ </cd-table-actions>
</cd-table>
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
-import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { ToastrModule } from 'ngx-toastr';
+import {
+ configureTestBed,
+ i18nProviders,
+ PermissionHelper
+} from '../../../../testing/unit-test-helper';
+import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component';
import { SharedModule } from '../../../shared/shared.module';
import { CephfsClientsComponent } from './cephfs-clients.component';
configureTestBed({
imports: [
RouterTestingModule,
+ ToastrModule.forRoot(),
BsDropdownModule.forRoot(),
SharedModule,
HttpClientTestingModule
beforeEach(() => {
fixture = TestBed.createComponent(CephfsClientsComponent);
component = fixture.componentInstance;
- fixture.detectChanges();
});
it('should create', () => {
+ fixture.detectChanges();
expect(component).toBeTruthy();
});
+
+ it('should test all TableActions combinations', () => {
+ const permissionHelper: PermissionHelper = new PermissionHelper(component.permission);
+ const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions(
+ component.tableActions
+ );
+
+ expect(tableActions).toEqual({
+ 'create,update,delete': {
+ actions: ['Evict'],
+ primary: { multiple: 'Evict', executing: 'Evict', single: 'Evict', no: 'Evict' }
+ },
+ 'create,update': {
+ actions: ['Evict'],
+ primary: { multiple: 'Evict', executing: 'Evict', single: 'Evict', no: 'Evict' }
+ },
+ 'create,delete': {
+ actions: [],
+ primary: { multiple: '', executing: '', single: '', no: '' }
+ },
+ create: {
+ actions: [],
+ primary: { multiple: '', executing: '', single: '', no: '' }
+ },
+ 'update,delete': {
+ actions: ['Evict'],
+ primary: { multiple: 'Evict', executing: 'Evict', single: 'Evict', no: 'Evict' }
+ },
+ update: {
+ actions: ['Evict'],
+ primary: { multiple: 'Evict', executing: 'Evict', single: 'Evict', no: 'Evict' }
+ },
+ delete: {
+ actions: [],
+ primary: { multiple: '', executing: '', single: '', no: '' }
+ },
+ 'no-permissions': {
+ actions: [],
+ primary: { multiple: '', executing: '', single: '', no: '' }
+ }
+ });
+ });
});
import { I18n } from '@ngx-translate/i18n-polyfill';
+import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { CephfsService } from '../../../shared/api/cephfs.service';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
+import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
+import { Icons } from '../../../shared/enum/icons.enum';
+import { NotificationType } from '../../../shared/enum/notification-type.enum';
import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
+import { CdTableAction } from '../../../shared/models/cd-table-action';
+import { CdTableSelection } from '../../../shared/models/cd-table-selection';
+import { Permission } from '../../../shared/models/permissions';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
+import { NotificationService } from '../../../shared/services/notification.service';
@Component({
selector: 'cd-cephfs-clients',
@Input()
id: number;
+ permission: Permission;
+ tableActions: CdTableAction[];
+ modalRef: BsModalRef;
clients: any;
viewCacheStatus: ViewCacheStatus;
+ selection = new CdTableSelection();
- constructor(private cephfsService: CephfsService, private i18n: I18n) {}
+ constructor(
+ private cephfsService: CephfsService,
+ private modalService: BsModalService,
+ private notificationService: NotificationService,
+ private authStorageService: AuthStorageService,
+ private i18n: I18n,
+ private actionLabels: ActionLabelsI18n
+ ) {
+ this.permission = this.authStorageService.getPermissions().cephfs;
+ const evictAction: CdTableAction = {
+ permission: 'update',
+ icon: Icons.signOut,
+ click: () => this.evictClientModal(),
+ name: this.actionLabels.EVICT
+ };
+ this.tableActions = [evictAction];
+ }
ngOnInit() {
this.clients = {
this.clients.data = data.data;
});
}
+
+ updateSelection(selection: CdTableSelection) {
+ this.selection = selection;
+ }
+
+ evictClient(clientId: number) {
+ this.cephfsService.evictClient(this.id, clientId).subscribe(
+ () => {
+ this.refresh();
+ this.modalRef.hide();
+ this.notificationService.show(
+ NotificationType.success,
+ this.i18n('Evicted client "{{clientId}}"', { clientId: clientId })
+ );
+ },
+ () => {
+ this.modalRef.content.stopLoadingSpinner();
+ }
+ );
+ }
+
+ evictClientModal() {
+ const clientId = this.selection.first().id;
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ initialState: {
+ itemDescription: 'client',
+ actionDescription: 'evict',
+ submitAction: () => this.evictClient(clientId)
+ }
+ });
+ }
}
return this.http.get(`${this.baseURL}/${id}/clients`);
}
+ evictClient(fsId, clientId) {
+ return this.http.delete(`${this.baseURL}/${fsId}/client/${clientId}`);
+ }
+
getMdsCounters(id) {
return this.http.get(`${this.baseURL}/${id}/mds_counters`);
}
COPY = 'Copy',
CLONE = 'Clone',
UPDATE = 'Update',
+ EVICT = 'Evict',
/* Read-only */
SHOW = 'Show',
CLONE: string;
DEEP_SCRUB: string;
DESTROY: string;
+ EVICT: string;
FLATTEN: string;
MARK_DOWN: string;
MARK_IN: string;
this.COPY = this.i18n('Copy');
this.DEEP_SCRUB = this.i18n('Deep Scrub');
this.DESTROY = this.i18n('Destroy');
+ this.EVICT = this.i18n('Evict');
this.FLATTEN = this.i18n('Flatten');
this.MARK_DOWN = this.i18n('Mark Down');
this.MARK_IN = this.i18n('Mark In');