Skip to content

Commit c3721f7

Browse files
authored
Merge pull request ceph#61040 from rhcs-dashboard/smb-module-check
mgr/module: Smb module check Reviewed-by: Nizamudeen A <[email protected]> Reviewed-by: Ankush Behl <[email protected]>
2 parents 3162e26 + 3123136 commit c3721f7

File tree

12 files changed

+229
-116
lines changed

12 files changed

+229
-116
lines changed

src/pybind/mgr/dashboard/controllers/smb.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from .. import mgr
1717
from ..security import Scope
18-
from . import APIDoc, APIRouter, ReadPermission, RESTController
18+
from . import APIDoc, APIRouter, Endpoint, ReadPermission, RESTController, UIRouter
1919

2020
logger = logging.getLogger('controllers.smb')
2121

@@ -184,3 +184,18 @@ def delete(self, cluster_id: str, share_id: str):
184184
resource['share_id'] = share_id
185185
resource['intent'] = Intent.REMOVED
186186
return mgr.remote('smb', 'apply_resources', json.dumps(resource)).one().to_simplified()
187+
188+
189+
@UIRouter('/smb')
190+
class SMBStatus(RESTController):
191+
@EndpointDoc("Get SMB feature status")
192+
@Endpoint()
193+
@ReadPermission
194+
def status(self):
195+
status = {'available': False, 'message': None}
196+
try:
197+
mgr.remote('smb', 'show', ['ceph.smb.cluster'])
198+
status['available'] = True
199+
except (ImportError, RuntimeError):
200+
status['message'] = 'SMB module is not enabled'
201+
return status

src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,15 @@ const routes: Routes = [
433433
},
434434
{
435435
path: 'smb',
436+
canActivateChild: [ModuleStatusGuardService],
436437
data: {
438+
moduleStatusGuardConfig: {
439+
uiApiPath: 'smb',
440+
redirectTo: 'error',
441+
header: 'SMB module is not enabled',
442+
button_to_enable_module: 'smb',
443+
navigate_to: 'cephfs/smb'
444+
},
437445
breadcrumbs: 'File/SMB'
438446
},
439447
children: [{ path: '', component: SmbClusterListComponent }]

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
44
import { SharedModule } from '~/app/shared/shared.module';
55
import { configureTestBed } from '~/testing/unit-test-helper';
66
import { MgrModuleDetailsComponent } from './mgr-module-details.component';
7+
import { ToastrModule } from 'ngx-toastr';
78

89
describe('MgrModuleDetailsComponent', () => {
910
let component: MgrModuleDetailsComponent;
1011
let fixture: ComponentFixture<MgrModuleDetailsComponent>;
1112

1213
configureTestBed({
1314
declarations: [MgrModuleDetailsComponent],
14-
imports: [HttpClientTestingModule, SharedModule]
15+
imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot()]
1516
});
1617

1718
beforeEach(() => {

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.spec.ts

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { of as observableOf, throwError as observableThrowError } from 'rxjs';
1010
import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
1111
import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
1212
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
13-
import { NotificationService } from '~/app/shared/services/notification.service';
1413
import { SharedModule } from '~/app/shared/shared.module';
1514
import { configureTestBed, PermissionHelper } from '~/testing/unit-test-helper';
1615
import { MgrModuleDetailsComponent } from '../mgr-module-details/mgr-module-details.component';
@@ -20,7 +19,6 @@ describe('MgrModuleListComponent', () => {
2019
let component: MgrModuleListComponent;
2120
let fixture: ComponentFixture<MgrModuleListComponent>;
2221
let mgrModuleService: MgrModuleService;
23-
let notificationService: NotificationService;
2422

2523
configureTestBed({
2624
declarations: [MgrModuleListComponent, MgrModuleDetailsComponent],
@@ -32,14 +30,13 @@ describe('MgrModuleListComponent', () => {
3230
NgbNavModule,
3331
ToastrModule.forRoot()
3432
],
35-
providers: [MgrModuleService, NotificationService]
33+
providers: [MgrModuleService]
3634
});
3735

3836
beforeEach(() => {
3937
fixture = TestBed.createComponent(MgrModuleListComponent);
4038
component = fixture.componentInstance;
4139
mgrModuleService = TestBed.inject(MgrModuleService);
42-
notificationService = TestBed.inject(NotificationService);
4340
});
4441

4542
it('should create', () => {
@@ -129,16 +126,13 @@ describe('MgrModuleListComponent', () => {
129126
});
130127
});
131128

132-
describe('should update module state', () => {
129+
describe('should update module state and component table', () => {
133130
beforeEach(() => {
134131
component.selection = new CdTableSelection();
135-
spyOn(notificationService, 'suspendToasties');
136-
spyOn(component.blockUI, 'start');
137-
spyOn(component.blockUI, 'stop');
138132
spyOn(component.table, 'refreshBtn');
139133
});
140134

141-
it('should enable module', fakeAsync(() => {
135+
it('should update selected module and refresh table', fakeAsync(() => {
142136
spyOn(mgrModuleService, 'enable').and.returnValue(observableThrowError('y'));
143137
spyOn(mgrModuleService, 'list').and.returnValues(observableThrowError('z'), observableOf([]));
144138
component.selection.add({
@@ -147,55 +141,11 @@ describe('MgrModuleListComponent', () => {
147141
always_on: false
148142
});
149143
component.updateModuleState();
150-
tick(2000);
151-
tick(2000);
144+
tick(mgrModuleService.REFRESH_INTERVAL);
145+
tick(mgrModuleService.REFRESH_INTERVAL);
152146
expect(mgrModuleService.enable).toHaveBeenCalledWith('foo');
153147
expect(mgrModuleService.list).toHaveBeenCalledTimes(2);
154-
expect(notificationService.suspendToasties).toHaveBeenCalledTimes(2);
155-
expect(component.blockUI.start).toHaveBeenCalled();
156-
expect(component.blockUI.stop).toHaveBeenCalled();
157148
expect(component.table.refreshBtn).toHaveBeenCalled();
158149
}));
159-
160-
it('should disable module', fakeAsync(() => {
161-
spyOn(mgrModuleService, 'disable').and.returnValue(observableThrowError('x'));
162-
spyOn(mgrModuleService, 'list').and.returnValue(observableOf([]));
163-
component.selection.add({
164-
name: 'bar',
165-
enabled: true,
166-
always_on: false
167-
});
168-
component.updateModuleState();
169-
tick(2000);
170-
expect(mgrModuleService.disable).toHaveBeenCalledWith('bar');
171-
expect(mgrModuleService.list).toHaveBeenCalledTimes(1);
172-
expect(notificationService.suspendToasties).toHaveBeenCalledTimes(2);
173-
expect(component.blockUI.start).toHaveBeenCalled();
174-
expect(component.blockUI.stop).toHaveBeenCalled();
175-
expect(component.table.refreshBtn).toHaveBeenCalled();
176-
}));
177-
178-
it('should not disable module without selecting one', () => {
179-
expect(component.getTableActionDisabledDesc()).toBeTruthy();
180-
});
181-
182-
it('should not disable dashboard module', () => {
183-
component.selection.selected = [
184-
{
185-
name: 'dashboard'
186-
}
187-
];
188-
expect(component.getTableActionDisabledDesc()).toBeTruthy();
189-
});
190-
191-
it('should not disable an always-on module', () => {
192-
component.selection.selected = [
193-
{
194-
name: 'bar',
195-
always_on: true
196-
}
197-
];
198-
expect(component.getTableActionDisabledDesc()).toBe('This Manager module is always on.');
199-
});
200150
});
201151
});

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.ts

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { Component, ViewChild } from '@angular/core';
22

3-
import { BlockUI, NgBlockUI } from 'ng-block-ui';
4-
import { timer as observableTimer } from 'rxjs';
5-
63
import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
74
import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
85
import { TableComponent } from '~/app/shared/datatable/table/table.component';
@@ -14,7 +11,6 @@ import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data
1411
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
1512
import { Permission } from '~/app/shared/models/permissions';
1613
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
17-
import { NotificationService } from '~/app/shared/services/notification.service';
1814

1915
@Component({
2016
selector: 'cd-mgr-module-list',
@@ -24,8 +20,6 @@ import { NotificationService } from '~/app/shared/services/notification.service'
2420
export class MgrModuleListComponent extends ListWithDetails {
2521
@ViewChild(TableComponent, { static: true })
2622
table: TableComponent;
27-
@BlockUI()
28-
blockUI: NgBlockUI;
2923

3024
permission: Permission;
3125
tableActions: CdTableAction[];
@@ -35,8 +29,7 @@ export class MgrModuleListComponent extends ListWithDetails {
3529

3630
constructor(
3731
private authStorageService: AuthStorageService,
38-
private mgrModuleService: MgrModuleService,
39-
private notificationService: NotificationService
32+
private mgrModuleService: MgrModuleService
4033
) {
4134
super();
4235
this.permission = this.authStorageService.getPermissions().configOpt;
@@ -147,52 +140,13 @@ export class MgrModuleListComponent extends ListWithDetails {
147140
}
148141

149142
/**
150-
* Update the Ceph Mgr module state to enabled or disabled.
143+
* Update the selected Ceph Mgr module state to enabled or disabled.
151144
*/
152145
updateModuleState() {
153146
if (!this.selection.hasSelection) {
154147
return;
155148
}
156-
157-
let $obs;
158-
const fnWaitUntilReconnected = () => {
159-
observableTimer(2000).subscribe(() => {
160-
// Trigger an API request to check if the connection is
161-
// re-established.
162-
this.mgrModuleService.list().subscribe(
163-
() => {
164-
// Resume showing the notification toasties.
165-
this.notificationService.suspendToasties(false);
166-
// Unblock the whole UI.
167-
this.blockUI.stop();
168-
// Reload the data table content.
169-
this.table.refreshBtn();
170-
},
171-
() => {
172-
fnWaitUntilReconnected();
173-
}
174-
);
175-
});
176-
};
177-
178-
// Note, the Ceph Mgr is always restarted when a module
179-
// is enabled/disabled.
180-
const module = this.selection.first();
181-
if (module.enabled) {
182-
$obs = this.mgrModuleService.disable(module.name);
183-
} else {
184-
$obs = this.mgrModuleService.enable(module.name);
185-
}
186-
$obs.subscribe(
187-
() => undefined,
188-
() => {
189-
// Suspend showing the notification toasties.
190-
this.notificationService.suspendToasties(true);
191-
// Block the whole UI to prevent user interactions until
192-
// the connection to the backend is reestablished
193-
this.blockUI.start($localize`Reconnecting, please wait ...`);
194-
fnWaitUntilReconnected();
195-
}
196-
);
149+
const selected = this.selection.first();
150+
this.mgrModuleService.updateModuleState(selected.name, selected.enabled, this.table);
197151
}
198152
}

src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class SmbClusterListComponent extends ListWithDetails implements OnInit {
5757

5858
this.smbClusters$ = this.subject$.pipe(
5959
switchMap(() =>
60-
this.smbService.listClusters().pipe(
60+
this.smbService.listClusters()?.pipe(
6161
catchError(() => {
6262
this.context.error();
6363
return of(null);

src/pybind/mgr/dashboard/frontend/src/app/core/error/error.component.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,19 @@ <h3 i18n><b>Page not Found</b></h3>
5959
</ng-template>
6060

6161
<ng-template #dashboardButton>
62-
<div class="mt-4 text-center">
62+
<div class="mt-4 text-center"
63+
*ngIf="!buttonToEnableModule; else enableButton">
6364
<button class="btn btn-primary"
6465
[routerLink]="'/dashboard'"
6566
i18n>Go To Dashboard</button>
6667
</div>
6768
</ng-template>
69+
70+
<ng-template #enableButton>
71+
<div class="mt-4 text-center"
72+
*ngIf="buttonToEnableModule && !(buttonName && buttonRoute)">
73+
<button class="btn btn-primary"
74+
(click)="enableModule()"
75+
i18n>Enable {{ buttonToEnableModule | upperFirst }} module</button>
76+
</div>
77+
</ng-template>

src/pybind/mgr/dashboard/frontend/src/app/core/error/error.component.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { NavigationEnd, Router, RouterEvent } from '@angular/router';
44

55
import { Subscription } from 'rxjs';
66
import { filter } from 'rxjs/operators';
7+
import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
78

89
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
910
import { DocService } from '~/app/shared/services/doc.service';
@@ -31,13 +32,16 @@ export class ErrorComponent implements OnDestroy, OnInit {
3132
secondaryButtonRoute: string;
3233
secondaryButtonName: string;
3334
secondaryButtonTitle: string;
35+
buttonToEnableModule: string;
36+
navigateTo: string;
3437
component: string;
3538

3639
constructor(
3740
private router: Router,
3841
private docService: DocService,
3942
private http: HttpClient,
40-
private notificationService: NotificationService
43+
private notificationService: NotificationService,
44+
private mgrModuleService: MgrModuleService
4145
) {}
4246

4347
ngOnInit() {
@@ -87,6 +91,8 @@ export class ErrorComponent implements OnDestroy, OnInit {
8791
this.secondaryButtonRoute = history.state.secondary_button_route;
8892
this.secondaryButtonName = history.state.secondary_button_name;
8993
this.secondaryButtonTitle = history.state.secondary_button_title;
94+
this.buttonToEnableModule = history.state.button_to_enable_module;
95+
this.navigateTo = history.state.navigate_to;
9096
this.component = history.state.component;
9197
this.docUrl = this.docService.urlGenerator(this.section);
9298
} catch (error) {
@@ -99,4 +105,13 @@ export class ErrorComponent implements OnDestroy, OnInit {
99105
this.routerSubscription.unsubscribe();
100106
}
101107
}
108+
109+
enableModule(): void {
110+
this.mgrModuleService.updateModuleState(
111+
this.buttonToEnableModule,
112+
false,
113+
null,
114+
this.navigateTo
115+
);
116+
}
102117
}

0 commit comments

Comments
 (0)