Skip to content
Permalink
Browse files

mgr/dashboard: Evict a CephFS client

Fixes: https://tracker.ceph.com/issues/24892

Signed-off-by: Ricardo Marques <rimarques@suse.com>
  • Loading branch information...
ricardoasmarques committed Jul 4, 2019
1 parent 92eaa76 commit 6fe2025b84c6b0227fd3f8506c45e8fdc1ad2111
@@ -27,6 +27,11 @@ def test_cephfs_clients(self):
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))
@@ -37,6 +37,13 @@ def clients(self, 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):
"""
@@ -86,6 +93,15 @@ def fs_id_to_int(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 = []

@@ -282,6 +298,15 @@ def _clients(self, fs_id):
'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):
@@ -2,5 +2,12 @@

<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>
@@ -4,7 +4,13 @@ import { RouterTestingModule } from '@angular/router/testing';

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';

@@ -15,6 +21,7 @@ describe('CephfsClientsComponent', () => {
configureTestBed({
imports: [
RouterTestingModule,
ToastrModule.forRoot(),
BsDropdownModule.forRoot(),
SharedModule,
HttpClientTestingModule
@@ -26,10 +33,52 @@ describe('CephfsClientsComponent', () => {
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: '' }
}
});
});
});
@@ -2,8 +2,18 @@ import { Component, Input, OnInit } from '@angular/core';

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',
@@ -14,10 +24,30 @@ export class CephfsClientsComponent implements OnInit {
@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 = {
@@ -42,4 +72,35 @@ export class CephfsClientsComponent implements OnInit {
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)
}
});
}
}
@@ -23,6 +23,10 @@ export class CephfsService {
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`);
}
@@ -58,6 +58,7 @@ export enum ActionLabels {
/* Non-standard actions */
COPY = 'Copy',
CLONE = 'Clone',
EVICT = 'Evict',

/* Read-only */
SHOW = 'Show',
@@ -84,6 +85,7 @@ export class ActionLabelsI18n {
CLONE: string;
DEEP_SCRUB: string;
DESTROY: string;
EVICT: string;
FLATTEN: string;
MARK_DOWN: string;
MARK_IN: string;
@@ -124,6 +126,7 @@ export class ActionLabelsI18n {
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');

0 comments on commit 6fe2025

Please sign in to comment.
You can’t perform that action at this time.