1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed } from '@angular/core/testing';
3 import { ReactiveFormsModule } from '@angular/forms';
4 import { By } from '@angular/platform-browser';
5 import { RouterTestingModule } from '@angular/router/testing';
7 import { NgbActiveModal, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
8 import _ from 'lodash';
9 import { ToastrModule } from 'ngx-toastr';
10 import { of } from 'rxjs';
12 import { CephServiceService } from '~/app/shared/api/ceph-service.service';
13 import { PaginateObservable } from '~/app/shared/api/paginate.model';
14 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
15 import { SharedModule } from '~/app/shared/shared.module';
16 import { configureTestBed, FormHelper, Mocks } from '~/testing/unit-test-helper';
17 import { ServiceFormComponent } from './service-form.component';
18 import { PoolService } from '~/app/shared/api/pool.service';
20 // for 'nvmeof' service
22 Mocks.getPool('pool-1', 1, ['cephfs']),
23 Mocks.getPool('rbd', 2),
24 Mocks.getPool('pool-2', 3)
26 class MockPoolService {
32 describe('ServiceFormComponent', () => {
33 let component: ServiceFormComponent;
34 let fixture: ComponentFixture<ServiceFormComponent>;
35 let cephServiceService: CephServiceService;
36 let form: CdFormGroup;
37 let formHelper: FormHelper;
40 declarations: [ServiceFormComponent],
41 providers: [NgbActiveModal, { provide: PoolService, useClass: MockPoolService }],
43 HttpClientTestingModule,
48 ToastrModule.forRoot()
53 fixture = TestBed.createComponent(ServiceFormComponent);
54 component = fixture.componentInstance;
56 form = component.serviceForm;
57 formHelper = new FormHelper(form);
58 fixture.detectChanges();
61 it('should create', () => {
62 expect(component).toBeTruthy();
65 describe('should test form', () => {
67 cephServiceService = TestBed.inject(CephServiceService);
68 spyOn(cephServiceService, 'create').and.stub();
71 it('should test placement (host)', () => {
72 formHelper.setValue('service_type', 'crash');
73 formHelper.setValue('placement', 'hosts');
74 formHelper.setValue('hosts', ['mgr0', 'mon0', 'osd0']);
75 formHelper.setValue('count', 2);
77 expect(cephServiceService.create).toHaveBeenCalledWith({
78 service_type: 'crash',
80 hosts: ['mgr0', 'mon0', 'osd0'],
87 it('should test placement (label)', () => {
88 formHelper.setValue('service_type', 'mgr');
89 formHelper.setValue('placement', 'label');
90 formHelper.setValue('label', 'foo');
92 expect(cephServiceService.create).toHaveBeenCalledWith({
101 it('should submit valid count', () => {
102 formHelper.setValue('count', 1);
103 component.onSubmit();
104 formHelper.expectValid('count');
107 it('should submit invalid count (1)', () => {
108 formHelper.setValue('count', 0);
109 component.onSubmit();
110 formHelper.expectError('count', 'min');
113 it('should submit invalid count (2)', () => {
114 formHelper.setValue('count', 'abc');
115 component.onSubmit();
116 formHelper.expectError('count', 'pattern');
119 it('should test unmanaged', () => {
120 formHelper.setValue('service_type', 'mgr');
121 formHelper.setValue('service_id', 'svc');
122 formHelper.setValue('placement', 'label');
123 formHelper.setValue('label', 'bar');
124 formHelper.setValue('unmanaged', true);
125 component.onSubmit();
126 expect(cephServiceService.create).toHaveBeenCalledWith({
134 it('should test various services', () => {
136 ['alertmanager', 'crash', 'mds', 'mgr', 'mon', 'node-exporter', 'prometheus', 'rbd-mirror'],
138 formHelper.setValue('service_type', serviceType);
139 component.onSubmit();
140 expect(cephServiceService.create).toHaveBeenCalledWith({
141 service_type: serviceType,
149 describe('should test service grafana', () => {
151 formHelper.setValue('service_type', 'grafana');
154 it('should sumbit grafana', () => {
155 component.onSubmit();
156 expect(cephServiceService.create).toHaveBeenCalledWith({
157 service_type: 'grafana',
160 initial_admin_password: null,
165 it('should sumbit grafana with custom port and initial password', () => {
166 formHelper.setValue('grafana_port', 1234);
167 formHelper.setValue('grafana_admin_password', 'foo');
168 component.onSubmit();
169 expect(cephServiceService.create).toHaveBeenCalledWith({
170 service_type: 'grafana',
173 initial_admin_password: 'foo',
179 describe('should test service nfs', () => {
181 formHelper.setValue('service_type', 'nfs');
184 it('should submit nfs', () => {
185 component.onSubmit();
186 expect(cephServiceService.create).toHaveBeenCalledWith({
194 describe('should test service rgw', () => {
196 formHelper.setValue('service_type', 'rgw');
197 formHelper.setValue('service_id', 'svc');
200 it('should test rgw valid service id', () => {
201 formHelper.setValue('service_id', 'svc.realm.zone');
202 formHelper.expectValid('service_id');
203 formHelper.setValue('service_id', 'svc');
204 formHelper.expectValid('service_id');
207 it('should submit rgw with realm, zonegroup and zone', () => {
208 formHelper.setValue('service_id', 'svc');
209 formHelper.setValue('realm_name', 'my-realm');
210 formHelper.setValue('zone_name', 'my-zone');
211 formHelper.setValue('zonegroup_name', 'my-zonegroup');
212 component.onSubmit();
213 expect(cephServiceService.create).toHaveBeenCalledWith({
216 rgw_realm: 'my-realm',
218 rgw_zonegroup: 'my-zonegroup',
225 it('should submit rgw with port and ssl enabled', () => {
226 formHelper.setValue('rgw_frontend_port', 1234);
227 formHelper.setValue('ssl', true);
228 component.onSubmit();
229 expect(cephServiceService.create).toHaveBeenCalledWith({
237 rgw_frontend_port: 1234,
238 rgw_frontend_ssl_certificate: '',
243 it('should submit valid rgw port (1)', () => {
244 formHelper.setValue('rgw_frontend_port', 1);
245 component.onSubmit();
246 formHelper.expectValid('rgw_frontend_port');
249 it('should submit valid rgw port (2)', () => {
250 formHelper.setValue('rgw_frontend_port', 65535);
251 component.onSubmit();
252 formHelper.expectValid('rgw_frontend_port');
255 it('should submit invalid rgw port (1)', () => {
256 formHelper.setValue('rgw_frontend_port', 0);
257 fixture.detectChanges();
258 formHelper.expectError('rgw_frontend_port', 'min');
261 it('should submit invalid rgw port (2)', () => {
262 formHelper.setValue('rgw_frontend_port', 65536);
263 fixture.detectChanges();
264 formHelper.expectError('rgw_frontend_port', 'max');
267 it('should submit invalid rgw port (3)', () => {
268 formHelper.setValue('rgw_frontend_port', 'abc');
269 component.onSubmit();
270 formHelper.expectError('rgw_frontend_port', 'pattern');
273 it('should submit rgw w/o port', () => {
274 formHelper.setValue('ssl', false);
275 component.onSubmit();
276 expect(cephServiceService.create).toHaveBeenCalledWith({
288 it('should not show private key field', () => {
289 formHelper.setValue('ssl', true);
290 fixture.detectChanges();
291 const ssl_key = fixture.debugElement.query(By.css('#ssl_key'));
292 expect(ssl_key).toBeNull();
295 it('should test .pem file', () => {
297 -----BEGIN CERTIFICATE-----
298 iJ5IbgzlKPssdYwuAEI3yPZxX/g5vKBrgcyD3LttLL/DlElq/1xCnwVrv7WROSNu
299 -----END CERTIFICATE-----
300 -----BEGIN CERTIFICATE-----
301 mn/S7BNBEC7AGe5ajmN+8hBTGdACUXe8rwMNrtTy/MwBZ0VpJsAAjJh+aptZh5yB
302 -----END CERTIFICATE-----
303 -----BEGIN RSA PRIVATE KEY-----
304 x4Ea7kGVgx9kWh5XjWz9wjZvY49UKIT5ppIAWPMbLl3UpfckiuNhTA==
305 -----END RSA PRIVATE KEY-----`;
306 formHelper.setValue('ssl', true);
307 formHelper.setValue('ssl_cert', pemCert);
308 fixture.detectChanges();
309 formHelper.expectValid('ssl_cert');
313 describe('should test service iscsi', () => {
315 formHelper.setValue('service_type', 'iscsi');
316 formHelper.setValue('pool', 'xyz');
317 formHelper.setValue('api_user', 'user');
318 formHelper.setValue('api_password', 'password');
319 formHelper.setValue('ssl', false);
322 it('should submit iscsi', () => {
323 component.onSubmit();
324 expect(cephServiceService.create).toHaveBeenCalledWith({
325 service_type: 'iscsi',
330 api_password: 'password',
335 it('should submit iscsi with trusted ips', () => {
336 formHelper.setValue('ssl', true);
337 formHelper.setValue('trusted_ip_list', ' 172.16.0.5, 192.1.1.10 ');
338 component.onSubmit();
339 expect(cephServiceService.create).toHaveBeenCalledWith({
340 service_type: 'iscsi',
345 api_password: 'password',
349 trusted_ip_list: '172.16.0.5, 192.1.1.10'
353 it('should submit iscsi with port', () => {
354 formHelper.setValue('api_port', 456);
355 component.onSubmit();
356 expect(cephServiceService.create).toHaveBeenCalledWith({
357 service_type: 'iscsi',
362 api_password: 'password',
368 it('should submit valid iscsi port (1)', () => {
369 formHelper.setValue('api_port', 1);
370 component.onSubmit();
371 formHelper.expectValid('api_port');
374 it('should submit valid iscsi port (2)', () => {
375 formHelper.setValue('api_port', 65535);
376 component.onSubmit();
377 formHelper.expectValid('api_port');
380 it('should submit invalid iscsi port (1)', () => {
381 formHelper.setValue('api_port', 0);
382 fixture.detectChanges();
383 formHelper.expectError('api_port', 'min');
386 it('should submit invalid iscsi port (2)', () => {
387 formHelper.setValue('api_port', 65536);
388 fixture.detectChanges();
389 formHelper.expectError('api_port', 'max');
392 it('should submit invalid iscsi port (3)', () => {
393 formHelper.setValue('api_port', 'abc');
394 component.onSubmit();
395 formHelper.expectError('api_port', 'pattern');
398 it('should throw error when there is no pool', () => {
399 formHelper.expectErrorChange('pool', '', 'required');
403 describe('should test service nvmeof', () => {
405 component.serviceType = 'nvmeof';
406 formHelper.setValue('service_type', 'nvmeof');
407 component.ngOnInit();
408 fixture.detectChanges();
411 it('should set rbd pools correctly onInit', () => {
412 expect(component.pools.length).toBe(3);
413 expect(component.rbdPools.length).toBe(2);
416 it('should set default values correctly onInit', () => {
417 expect(form.get('service_type').value).toBe('nvmeof');
418 expect(form.get('group').value).toBe('default');
419 expect(form.get('pool').value).toBe('rbd');
420 expect(form.get('service_id').value).toBe('rbd.default');
423 it('should reflect correct values on group change', () => {
424 // Initially the group value should be 'default'
425 expect(component.serviceForm.get('group')?.value).toBe('default');
426 const groupInput = fixture.debugElement.query(By.css('#group')).nativeElement;
427 // Simulate input value change
428 groupInput.value = 'foo';
429 // Trigger the input event
430 groupInput.dispatchEvent(new Event('input'));
431 // Trigger the change event
432 groupInput.dispatchEvent(new Event('change'));
433 fixture.detectChanges();
434 // Verify values after change
435 expect(form.get('group').value).toBe('foo');
436 expect(form.get('service_id').value).toBe('rbd.foo');
439 it('should reflect correct values on pool change', () => {
440 // Initially the pool value should be 'rbd'
441 expect(component.serviceForm.get('pool')?.value).toBe('rbd');
442 const poolInput = fixture.debugElement.query(By.css('#pool')).nativeElement;
443 // Simulate input value change
444 poolInput.value = 'pool-2';
445 // Trigger the input event
446 poolInput.dispatchEvent(new Event('input'));
447 // Trigger the change event
448 poolInput.dispatchEvent(new Event('change'));
449 fixture.detectChanges();
450 // Verify values after change
451 expect(form.get('pool').value).toBe('pool-2');
452 expect(form.get('service_id').value).toBe('pool-2.default');
455 it('should throw error when there is no service id', () => {
456 formHelper.expectErrorChange('service_id', '', 'required');
459 it('should throw error when there is no pool', () => {
460 formHelper.expectErrorChange('pool', '', 'required');
463 it('should throw error when there is no group', () => {
464 formHelper.expectErrorChange('group', '', 'required');
467 it('should hide the count element when service_type is "nvmeof"', () => {
468 const countEl = fixture.debugElement.query(By.css('#count'));
469 expect(countEl).toBeNull();
472 it('should not show certs and keys field with mTLS disabled', () => {
473 formHelper.setValue('ssl', true);
474 fixture.detectChanges();
475 const root_ca_cert = fixture.debugElement.query(By.css('#root_ca_cert'));
476 const client_cert = fixture.debugElement.query(By.css('#client_cert'));
477 const client_key = fixture.debugElement.query(By.css('#client_key'));
478 const server_cert = fixture.debugElement.query(By.css('#server_cert'));
479 const server_key = fixture.debugElement.query(By.css('#server_key'));
480 expect(root_ca_cert).toBeNull();
481 expect(client_cert).toBeNull();
482 expect(client_key).toBeNull();
483 expect(server_cert).toBeNull();
484 expect(server_key).toBeNull();
487 it('should submit nvmeof without mTLS', () => {
488 component.onSubmit();
489 expect(cephServiceService.create).toHaveBeenCalledWith({
490 service_type: 'nvmeof',
491 service_id: 'rbd.default',
500 it('should submit nvmeof with mTLS', () => {
501 formHelper.setValue('enable_mtls', true);
502 formHelper.setValue('root_ca_cert', 'root_ca_cert');
503 formHelper.setValue('client_cert', 'client_cert');
504 formHelper.setValue('client_key', 'client_key');
505 formHelper.setValue('server_cert', 'server_cert');
506 formHelper.setValue('server_key', 'server_key');
507 component.onSubmit();
508 expect(cephServiceService.create).toHaveBeenCalledWith({
509 service_type: 'nvmeof',
510 service_id: 'rbd.default',
516 root_ca_cert: 'root_ca_cert',
517 client_cert: 'client_cert',
518 client_key: 'client_key',
519 server_cert: 'server_cert',
520 server_key: 'server_key'
525 describe('should test service smb', () => {
527 formHelper.setValue('service_type', 'smb');
528 formHelper.setValue('service_id', 'foo');
529 formHelper.setValue('cluster_id', 'cluster_foo');
530 formHelper.setValue('config_uri', 'rados://.smb/foo/scc.toml');
533 it('should submit smb', () => {
534 component.onSubmit();
535 expect(cephServiceService.create).toHaveBeenCalledWith({
540 cluster_id: 'cluster_foo',
541 config_uri: 'rados://.smb/foo/scc.toml'
546 describe('should test service ingress', () => {
548 formHelper.setValue('service_type', 'ingress');
549 formHelper.setValue('backend_service', 'rgw.foo');
550 formHelper.setValue('virtual_ip', '192.168.20.1/24');
551 formHelper.setValue('ssl', false);
554 it('should submit ingress', () => {
555 component.onSubmit();
556 expect(cephServiceService.create).toHaveBeenCalledWith({
557 service_type: 'ingress',
560 backend_service: 'rgw.foo',
561 service_id: 'rgw.foo',
562 virtual_ip: '192.168.20.1/24',
563 virtual_interface_networks: null,
568 it('should pre-populate the service id', () => {
569 component.prePopulateId();
570 const prePopulatedID = component.serviceForm.getValue('service_id');
571 expect(prePopulatedID).toBe('rgw.foo');
574 it('should submit valid frontend and monitor port', () => {
576 formHelper.setValue('frontend_port', 1);
577 formHelper.setValue('monitor_port', 1);
578 fixture.detectChanges();
579 formHelper.expectValid('frontend_port');
580 formHelper.expectValid('monitor_port');
583 formHelper.setValue('frontend_port', 65535);
584 formHelper.setValue('monitor_port', 65535);
585 fixture.detectChanges();
586 formHelper.expectValid('frontend_port');
587 formHelper.expectValid('monitor_port');
590 it('should submit invalid frontend and monitor port', () => {
592 formHelper.setValue('frontend_port', 0);
593 formHelper.setValue('monitor_port', 0);
594 fixture.detectChanges();
595 formHelper.expectError('frontend_port', 'min');
596 formHelper.expectError('monitor_port', 'min');
599 formHelper.setValue('frontend_port', 65536);
600 formHelper.setValue('monitor_port', 65536);
601 fixture.detectChanges();
602 formHelper.expectError('frontend_port', 'max');
603 formHelper.expectError('monitor_port', 'max');
606 formHelper.setValue('frontend_port', 'abc');
607 formHelper.setValue('monitor_port', 'abc');
608 component.onSubmit();
609 formHelper.expectError('frontend_port', 'pattern');
610 formHelper.expectError('monitor_port', 'pattern');
613 it('should not show private key field with ssl enabled', () => {
614 formHelper.setValue('ssl', true);
615 fixture.detectChanges();
616 const ssl_key = fixture.debugElement.query(By.css('#ssl_key'));
617 expect(ssl_key).toBeNull();
620 it('should test .pem file with ssl enabled', () => {
622 -----BEGIN CERTIFICATE-----
623 iJ5IbgzlKPssdYwuAEI3yPZxX/g5vKBrgcyD3LttLL/DlElq/1xCnwVrv7WROSNu
624 -----END CERTIFICATE-----
625 -----BEGIN CERTIFICATE-----
626 mn/S7BNBEC7AGe5ajmN+8hBTGdACUXe8rwMNrtTy/MwBZ0VpJsAAjJh+aptZh5yB
627 -----END CERTIFICATE-----
628 -----BEGIN RSA PRIVATE KEY-----
629 x4Ea7kGVgx9kWh5XjWz9wjZvY49UKIT5ppIAWPMbLl3UpfckiuNhTA==
630 -----END RSA PRIVATE KEY-----`;
631 formHelper.setValue('ssl', true);
632 formHelper.setValue('ssl_cert', pemCert);
633 fixture.detectChanges();
634 formHelper.expectValid('ssl_cert');
638 describe('should test service snmp-gateway', () => {
640 formHelper.setValue('service_type', 'snmp-gateway');
641 formHelper.setValue('snmp_destination', '192.168.20.1:8443');
644 it('should test snmp-gateway service with V2c', () => {
645 formHelper.setValue('snmp_version', 'V2c');
646 formHelper.setValue('snmp_community', 'public');
647 component.onSubmit();
648 expect(cephServiceService.create).toHaveBeenCalledWith({
649 service_type: 'snmp-gateway',
653 snmp_destination: '192.168.20.1:8443',
655 snmp_community: 'public'
660 it('should test snmp-gateway service with V3', () => {
661 formHelper.setValue('snmp_version', 'V3');
662 formHelper.setValue('engine_id', '800C53F00000');
663 formHelper.setValue('auth_protocol', 'SHA');
664 formHelper.setValue('privacy_protocol', 'DES');
665 formHelper.setValue('snmp_v3_auth_username', 'testuser');
666 formHelper.setValue('snmp_v3_auth_password', 'testpass');
667 formHelper.setValue('snmp_v3_priv_password', 'testencrypt');
668 component.onSubmit();
669 expect(cephServiceService.create).toHaveBeenCalledWith({
670 service_type: 'snmp-gateway',
674 snmp_destination: '192.168.20.1:8443',
675 engine_id: '800C53F00000',
676 auth_protocol: 'SHA',
677 privacy_protocol: 'DES',
679 snmp_v3_auth_username: 'testuser',
680 snmp_v3_auth_password: 'testpass',
681 snmp_v3_priv_password: 'testencrypt'
686 it('should submit invalid snmp destination', () => {
687 formHelper.setValue('snmp_version', 'V2c');
688 formHelper.setValue('snmp_destination', '192.168.20.1');
689 formHelper.setValue('snmp_community', 'public');
690 formHelper.expectError('snmp_destination', 'snmpDestinationPattern');
693 it('should submit invalid snmp engine id', () => {
694 formHelper.setValue('snmp_version', 'V3');
695 formHelper.setValue('snmp_destination', '192.168.20.1');
696 formHelper.setValue('engine_id', 'AABBCCDDE');
697 formHelper.setValue('auth_protocol', 'SHA');
698 formHelper.setValue('privacy_protocol', 'DES');
699 formHelper.setValue('snmp_v3_auth_username', 'testuser');
700 formHelper.setValue('snmp_v3_auth_password', 'testpass');
702 formHelper.expectError('engine_id', 'snmpEngineIdPattern');
706 describe('should test service mds', () => {
708 formHelper.setValue('service_type', 'mds');
709 const paginate_obs = new PaginateObservable<any>(of({}));
710 spyOn(cephServiceService, 'list').and.returnValue(paginate_obs);
713 it('should test mds valid service id', () => {
714 formHelper.setValue('service_id', 'svc123');
715 formHelper.expectValid('service_id');
716 formHelper.setValue('service_id', 'svc_id-1');
717 formHelper.expectValid('service_id');
720 it('should test mds invalid service id', () => {
721 formHelper.setValue('service_id', '123');
722 formHelper.expectError('service_id', 'mdsPattern');
723 formHelper.setValue('service_id', '123svc');
724 formHelper.expectError('service_id', 'mdsPattern');
725 formHelper.setValue('service_id', 'svc#1');
726 formHelper.expectError('service_id', 'mdsPattern');
730 describe('check edit fields', () => {
732 component.editing = true;
735 it('should check whether edit field is correctly loaded', () => {
736 const paginate_obs = new PaginateObservable<any>(of({}));
737 const cephServiceSpy = spyOn(cephServiceService, 'list').and.returnValue(paginate_obs);
738 component.ngOnInit();
739 expect(cephServiceSpy).toBeCalledTimes(2);
740 expect(component.action).toBe('Edit');
741 const serviceType = fixture.debugElement.query(By.css('#service_type')).nativeElement;
742 const serviceId = fixture.debugElement.query(By.css('#service_id')).nativeElement;
743 expect(serviceType.disabled).toBeTruthy();
744 expect(serviceId.disabled).toBeTruthy();
747 it('should not edit pools for nvmeof service', () => {
748 component.serviceType = 'nvmeof';
749 formHelper.setValue('service_type', 'nvmeof');
750 component.ngOnInit();
751 fixture.detectChanges();
752 const poolId = fixture.debugElement.query(By.css('#pool')).nativeElement;
753 expect(poolId.disabled).toBeTruthy();
756 it('should not edit groups for nvmeof service', () => {
757 component.serviceType = 'nvmeof';
758 formHelper.setValue('service_type', 'nvmeof');
759 component.ngOnInit();
760 fixture.detectChanges();
761 const groupId = fixture.debugElement.query(By.css('#group')).nativeElement;
762 expect(groupId.disabled).toBeTruthy();
765 it('should update nvmeof service to disable mTLS', () => {
766 spyOn(cephServiceService, 'update').and.stub();
767 component.serviceType = 'nvmeof';
768 formHelper.setValue('service_type', 'nvmeof');
769 formHelper.setValue('pool', 'rbd');
770 formHelper.setValue('group', 'default');
772 formHelper.setValue('enable_auth', false);
773 component.onSubmit();
774 expect(cephServiceService.update).toHaveBeenCalledWith({
775 service_type: 'nvmeof',