]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts
import ceph pacific 16.2.5
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / hosts / hosts.component.spec.ts
CommitLineData
11fdf7f2 1import { HttpClientTestingModule } from '@angular/common/http/testing';
f67539c2 2import { ComponentFixture, TestBed } from '@angular/core/testing';
e306af50 3import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
11fdf7f2
TL
4import { RouterTestingModule } from '@angular/router/testing';
5
9f95a23c
TL
6import { ToastrModule } from 'ngx-toastr';
7import { of } from 'rxjs';
11fdf7f2 8
f67539c2
TL
9import { CephModule } from '~/app/ceph/ceph.module';
10import { CephSharedModule } from '~/app/ceph/shared/ceph-shared.module';
11import { CoreModule } from '~/app/core/core.module';
12import { HostService } from '~/app/shared/api/host.service';
13import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
14import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
15import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
16import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum';
17import { Permissions } from '~/app/shared/models/permissions';
18import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
19import { SharedModule } from '~/app/shared/shared.module';
20import {
21 configureTestBed,
22 OrchestratorHelper,
23 TableActionHelper
24} from '~/testing/unit-test-helper';
11fdf7f2
TL
25import { HostsComponent } from './hosts.component';
26
b3b6e05e
TL
27class MockShowForceMaintenanceModal {
28 showModal = false;
29 showModalDialog(msg: string) {
30 if (
31 msg.includes('WARNING') &&
32 !msg.includes('It is NOT safe to stop') &&
33 !msg.includes('ALERT') &&
34 !msg.includes('unable to stop')
35 ) {
36 this.showModal = true;
37 }
38 }
39}
40
11fdf7f2
TL
41describe('HostsComponent', () => {
42 let component: HostsComponent;
43 let fixture: ComponentFixture<HostsComponent>;
9f95a23c 44 let hostListSpy: jasmine.Spy;
f67539c2 45 let orchService: OrchestratorService;
b3b6e05e 46 let showForceMaintenanceModal: MockShowForceMaintenanceModal;
11fdf7f2
TL
47
48 const fakeAuthStorageService = {
49 getPermissions: () => {
50 return new Permissions({ hosts: ['read', 'update', 'create', 'delete'] });
51 }
52 };
53
54 configureTestBed({
55 imports: [
e306af50 56 BrowserAnimationsModule,
9f95a23c 57 CephSharedModule,
11fdf7f2
TL
58 SharedModule,
59 HttpClientTestingModule,
9f95a23c
TL
60 RouterTestingModule,
61 ToastrModule.forRoot(),
62 CephModule,
63 CoreModule
11fdf7f2 64 ],
f67539c2
TL
65 providers: [
66 { provide: AuthStorageService, useValue: fakeAuthStorageService },
67 TableActionsComponent
68 ]
11fdf7f2
TL
69 });
70
71 beforeEach(() => {
b3b6e05e 72 showForceMaintenanceModal = new MockShowForceMaintenanceModal();
11fdf7f2
TL
73 fixture = TestBed.createComponent(HostsComponent);
74 component = fixture.componentInstance;
f67539c2
TL
75 hostListSpy = spyOn(TestBed.inject(HostService), 'list');
76 orchService = TestBed.inject(OrchestratorService);
11fdf7f2
TL
77 });
78
79 it('should create', () => {
80 expect(component).toBeTruthy();
81 });
82
f67539c2 83 it('should render hosts list even with not permission mapped services', () => {
11fdf7f2
TL
84 const hostname = 'ceph.dev';
85 const payload = [
86 {
87 services: [
88 {
89 type: 'osd',
90 id: '0'
91 },
92 {
93 type: 'rgw',
94 id: 'rgw'
95 },
96 {
97 type: 'notPermissionMappedService',
98 id: '1'
99 }
100 ],
101 hostname: hostname,
e306af50
TL
102 ceph_version: 'ceph version Development',
103 labels: ['foo', 'bar']
11fdf7f2
TL
104 }
105 ];
106
f67539c2 107 OrchestratorHelper.mockStatus(true);
9f95a23c 108 hostListSpy.and.callFake(() => of(payload));
f67539c2 109 fixture.detectChanges();
11fdf7f2 110
f67539c2 111 return fixture.whenStable().then(() => {
11fdf7f2
TL
112 fixture.detectChanges();
113
114 const spans = fixture.debugElement.nativeElement.querySelectorAll(
115 '.datatable-body-cell-label span'
116 );
117 expect(spans[0].textContent).toBe(hostname);
118 });
f67539c2 119 });
f6b5b4d7 120
b3b6e05e
TL
121 it('should show force maintenance modal when it is safe to stop host', () => {
122 const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service.
123 Service will not be operational with no daemons left. At
124 least 1 daemon must be running to guarantee service.`;
125 showForceMaintenanceModal.showModalDialog(errorMsg);
126 expect(showForceMaintenanceModal.showModal).toBeTruthy();
127 });
128
129 it('should not show force maintenance modal when error is an ALERT', () => {
130 const errorMsg = `ALERT: Cannot stop active Mgr daemon, Please switch active Mgrs
131 with 'ceph mgr fail ceph-node-00'`;
132 showForceMaintenanceModal.showModalDialog(errorMsg);
133 expect(showForceMaintenanceModal.showModal).toBeFalsy();
134 });
135
136 it('should not show force maintenance modal when it is not safe to stop host', () => {
137 const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service.
138 Service will not be operational with no daemons left. At
139 least 1 daemon must be running to guarantee service.
140 It is NOT safe to stop ['mon.ceph-node-00']: not enough
141 monitors would be available (ceph-node-02) after stopping mons`;
142 showForceMaintenanceModal.showModalDialog(errorMsg);
143 expect(showForceMaintenanceModal.showModal).toBeFalsy();
144 });
145
146 it('should not show force maintenance modal when it is unable to stop host', () => {
147 const errorMsg = 'unable to stop osd.0 because of some unknown reason';
148 showForceMaintenanceModal.showModalDialog(errorMsg);
149 expect(showForceMaintenanceModal.showModal).toBeFalsy();
150 });
151
f67539c2
TL
152 describe('table actions', () => {
153 const fakeHosts = require('./fixtures/host_list_response.json');
f6b5b4d7
TL
154
155 beforeEach(() => {
f67539c2 156 hostListSpy.and.callFake(() => of(fakeHosts));
f6b5b4d7
TL
157 });
158
f67539c2
TL
159 const testTableActions = async (
160 orch: boolean,
161 features: OrchestratorFeature[],
162 tests: { selectRow?: number; expectResults: any }[]
163 ) => {
164 OrchestratorHelper.mockStatus(orch, features);
165 fixture.detectChanges();
166 await fixture.whenStable();
f91f0fd5 167
f67539c2
TL
168 for (const test of tests) {
169 if (test.selectRow) {
170 component.selection = new CdTableSelection();
171 component.selection.selected = [test.selectRow];
f91f0fd5 172 }
f67539c2
TL
173 await TableActionHelper.verifyTableActions(
174 fixture,
175 component.tableActions,
176 test.expectResults
177 );
178 }
179 };
180
181 it('should have correct states when Orchestrator is enabled', async () => {
182 const tests = [
183 {
184 expectResults: {
185 Create: { disabled: false, disableDesc: '' },
186 Edit: { disabled: true, disableDesc: '' },
187 Delete: { disabled: true, disableDesc: '' }
188 }
189 },
190 {
191 selectRow: fakeHosts[0], // non-orchestrator host
192 expectResults: {
193 Create: { disabled: false, disableDesc: '' },
194 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
195 Delete: { disabled: true, disableDesc: component.messages.nonOrchHost }
196 }
197 },
198 {
199 selectRow: fakeHosts[1], // orchestrator host
200 expectResults: {
201 Create: { disabled: false, disableDesc: '' },
202 Edit: { disabled: false, disableDesc: '' },
203 Delete: { disabled: false, disableDesc: '' }
204 }
f91f0fd5 205 }
f67539c2
TL
206 ];
207
208 const features = [
209 OrchestratorFeature.HOST_CREATE,
210 OrchestratorFeature.HOST_LABEL_ADD,
211 OrchestratorFeature.HOST_DELETE,
212 OrchestratorFeature.HOST_LABEL_REMOVE
213 ];
214 await testTableActions(true, features, tests);
f91f0fd5
TL
215 });
216
f67539c2
TL
217 it('should have correct states when Orchestrator is disabled', async () => {
218 const resultNoOrchestrator = {
219 disabled: true,
220 disableDesc: orchService.disableMessages.noOrchestrator
221 };
222 const tests = [
223 {
224 expectResults: {
225 Create: resultNoOrchestrator,
226 Edit: { disabled: true, disableDesc: '' },
227 Delete: { disabled: true, disableDesc: '' }
228 }
229 },
230 {
231 selectRow: fakeHosts[0], // non-orchestrator host
232 expectResults: {
233 Create: resultNoOrchestrator,
234 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
235 Delete: { disabled: true, disableDesc: component.messages.nonOrchHost }
236 }
237 },
238 {
239 selectRow: fakeHosts[1], // orchestrator host
240 expectResults: {
241 Create: resultNoOrchestrator,
242 Edit: resultNoOrchestrator,
243 Delete: resultNoOrchestrator
244 }
245 }
246 ];
247 await testTableActions(false, [], tests);
f91f0fd5
TL
248 });
249
f67539c2
TL
250 it('should have correct states when Orchestrator features are missing', async () => {
251 const resultMissingFeatures = {
252 disabled: true,
253 disableDesc: orchService.disableMessages.missingFeature
254 };
255 const tests = [
256 {
257 expectResults: {
258 Create: resultMissingFeatures,
259 Edit: { disabled: true, disableDesc: '' },
260 Delete: { disabled: true, disableDesc: '' }
261 }
262 },
263 {
264 selectRow: fakeHosts[0], // non-orchestrator host
265 expectResults: {
266 Create: resultMissingFeatures,
267 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
268 Delete: { disabled: true, disableDesc: component.messages.nonOrchHost }
269 }
270 },
271 {
272 selectRow: fakeHosts[1], // orchestrator host
273 expectResults: {
274 Create: resultMissingFeatures,
275 Edit: resultMissingFeatures,
276 Delete: resultMissingFeatures
277 }
f91f0fd5 278 }
f67539c2
TL
279 ];
280 await testTableActions(true, [], tests);
f6b5b4d7
TL
281 });
282 });
11fdf7f2 283});