Skip to content

Commit 9bf5b9f

Browse files
authored
Merge pull request ceph#62869 from afreen23/wip-nvme
mgr/dashboard: Fix pool update on edit Reviewed-by: Nizamudeen A <[email protected]>
2 parents c5041a0 + aeeaf73 commit 9bf5b9f

File tree

4 files changed

+140
-32
lines changed

4 files changed

+140
-32
lines changed

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-form/nvmeof-namespaces-form.component.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
<input *ngIf="edit"
2020
class="form-control"
2121
type="text"
22+
id="pool-edit"
2223
formControlName="pool">
2324
<select *ngIf="!edit"
24-
id="pool"
25+
id="pool-create"
2526
class="form-select"
2627
formControlName="pool">
2728
<option *ngIf="rbdPools === null"
@@ -46,7 +47,8 @@
4647
</div>
4748
<!-- Namespace Count -->
4849
<div *ngIf="!edit"
49-
class="form-group row">
50+
class="form-group row"
51+
id="namespace-count">
5052
<label class="cd-col-form-label"
5153
for="nsCount">
5254
<span [ngClass]="{'required': !edit}"
@@ -93,6 +95,7 @@
9395
<ng-container i18n>This field is required</ng-container>
9496
</span>
9597
<span class="invalid-feedback"
98+
id="image-size-invalid"
9699
*ngIf="edit && invalidSizeError">
97100
<ng-container i18n>Enter a value above than previous. A block device image can be expanded but not reduced.</ng-container>
98101
</span>

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-form/nvmeof-namespaces-form.component.spec.ts

Lines changed: 118 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,68 @@ import { NvmeofService } from '~/app/shared/api/nvmeof.service';
1616
import { of } from 'rxjs';
1717
import { PoolService } from '~/app/shared/api/pool.service';
1818
import { NumberModule } from 'carbon-components-angular';
19+
import { ActivatedRoute, Router } from '@angular/router';
20+
import { By } from '@angular/platform-browser';
21+
import { ActivatedRouteStub } from '~/testing/activated-route-stub';
1922

20-
const mockPools = [
23+
const MOCK_POOLS = [
2124
Mocks.getPool('pool-1', 1, ['cephfs']),
2225
Mocks.getPool('rbd', 2),
2326
Mocks.getPool('pool-2', 3)
2427
];
25-
class MockPoolService {
28+
class MockPoolsService {
2629
getList() {
27-
return of(mockPools);
30+
return of(MOCK_POOLS);
2831
}
2932
}
3033

34+
const MOCK_NS_RESPONSE = {
35+
nsid: 1,
36+
uuid: '185d541f-76bf-45b5-b445-f71829346c38',
37+
bdev_name: 'bdev_185d541f-76bf-45b5-b445-f71829346c38',
38+
rbd_image_name: 'nvme_rbd_default_sscfagwuvvr',
39+
rbd_pool_name: 'rbd',
40+
load_balancing_group: 1,
41+
rbd_image_size: '1073741824',
42+
block_size: 512,
43+
rw_ios_per_second: '0',
44+
rw_mbytes_per_second: '0',
45+
r_mbytes_per_second: '0',
46+
w_mbytes_per_second: '0',
47+
trash_image: false
48+
};
49+
50+
const MOCK_ROUTER = {
51+
editUrl:
52+
'https://192.168.100.100:8443/#/block/nvmeof/subsystems/(modal:edit/nqn.2001-07.com.ceph:1744881547418.default/namespace/1)?group=default',
53+
createUrl: 'https://192.168.100.100:8443/#/block/nvmeof/subsystems/(modal:create)?group=default'
54+
};
55+
3156
describe('NvmeofNamespacesFormComponent', () => {
3257
let component: NvmeofNamespacesFormComponent;
3358
let fixture: ComponentFixture<NvmeofNamespacesFormComponent>;
3459
let nvmeofService: NvmeofService;
60+
let router: Router;
3561
let form: CdFormGroup;
3662
let formHelper: FormHelper;
37-
const mockRandomString = 1720693470789;
38-
const mockSubsystemNQN = 'nqn.2021-11.com.example:subsystem';
39-
const mockGWgroup = 'default';
63+
let activatedRouteStub: ActivatedRouteStub;
64+
const MOCK_RANDOM_STRING = 1720693470789;
65+
const MOCK_SUBSYSTEM = 'nqn.2021-11.com.example:subsystem';
66+
const MOCK_GROUP = 'default';
67+
const MOCK_NSID = String(MOCK_NS_RESPONSE['nsid']);
4068

4169
beforeEach(async () => {
70+
activatedRouteStub = new ActivatedRouteStub(
71+
{ subsystem_nqn: MOCK_SUBSYSTEM, nsid: MOCK_NSID },
72+
{ group: MOCK_GROUP }
73+
);
4274
await TestBed.configureTestingModule({
4375
declarations: [NvmeofNamespacesFormComponent],
44-
providers: [NgbActiveModal, { provide: PoolService, useClass: MockPoolService }],
76+
providers: [
77+
NgbActiveModal,
78+
{ provide: PoolService, useClass: MockPoolsService },
79+
{ provide: ActivatedRoute, useValue: activatedRouteStub }
80+
],
4581
imports: [
4682
HttpClientTestingModule,
4783
NgbTypeaheadModule,
@@ -54,30 +90,36 @@ describe('NvmeofNamespacesFormComponent', () => {
5490
}).compileComponents();
5591
fixture = TestBed.createComponent(NvmeofNamespacesFormComponent);
5692
component = fixture.componentInstance;
57-
component.ngOnInit();
58-
form = component.nsForm;
59-
formHelper = new FormHelper(form);
60-
fixture.detectChanges();
6193
});
6294

63-
it('should create', () => {
95+
it('should create component', () => {
6496
expect(component).toBeTruthy();
6597
});
66-
67-
describe('should test form', () => {
98+
describe('should test create form', () => {
6899
beforeEach(() => {
69-
component.subsystemNQN = mockSubsystemNQN;
70-
component.group = mockGWgroup;
100+
router = TestBed.inject(Router);
71101
nvmeofService = TestBed.inject(NvmeofService);
72102
spyOn(nvmeofService, 'createNamespace').and.stub();
73-
spyOn(component, 'randomString').and.returnValue(mockRandomString);
103+
spyOn(component, 'randomString').and.returnValue(MOCK_RANDOM_STRING);
104+
Object.defineProperty(router, 'url', {
105+
get: jasmine.createSpy('url').and.returnValue(MOCK_ROUTER.createUrl)
106+
});
107+
component.ngOnInit();
108+
form = component.nsForm;
109+
formHelper = new FormHelper(form);
110+
});
111+
it('should have set create fields correctly', () => {
112+
expect(component.rbdPools.length).toBe(2);
113+
fixture.detectChanges();
114+
const poolEl = fixture.debugElement.query(By.css('#pool-create')).nativeElement;
115+
expect(poolEl.value).toBe('rbd');
74116
});
75117
it('should create 5 namespaces correctly', () => {
76118
component.onSubmit();
77119
expect(nvmeofService.createNamespace).toHaveBeenCalledTimes(5);
78-
expect(nvmeofService.createNamespace).toHaveBeenCalledWith(mockSubsystemNQN, {
79-
gw_group: mockGWgroup,
80-
rbd_image_name: `nvme_rbd_default_${mockRandomString}`,
120+
expect(nvmeofService.createNamespace).toHaveBeenCalledWith(MOCK_SUBSYSTEM, {
121+
gw_group: MOCK_GROUP,
122+
rbd_image_name: `nvme_rbd_default_${MOCK_RANDOM_STRING}`,
81123
rbd_pool: 'rbd',
82124
rbd_image_size: 1073741824
83125
});
@@ -93,4 +135,60 @@ describe('NvmeofNamespacesFormComponent', () => {
93135
formHelper.expectError('image_size', 'min');
94136
});
95137
});
138+
describe('should test edit form', () => {
139+
beforeEach(() => {
140+
router = TestBed.inject(Router);
141+
nvmeofService = TestBed.inject(NvmeofService);
142+
spyOn(nvmeofService, 'getNamespace').and.returnValue(of(MOCK_NS_RESPONSE));
143+
spyOn(nvmeofService, 'updateNamespace').and.stub();
144+
Object.defineProperty(router, 'url', {
145+
get: jasmine.createSpy('url').and.returnValue(MOCK_ROUTER.editUrl)
146+
});
147+
fixture.detectChanges();
148+
});
149+
it('should have set edit fields correctly', () => {
150+
expect(nvmeofService.getNamespace).toHaveBeenCalledTimes(1);
151+
const poolEl = fixture.debugElement.query(By.css('#pool-edit')).nativeElement;
152+
expect(poolEl.disabled).toBeTruthy();
153+
expect(poolEl.value).toBe(MOCK_NS_RESPONSE['rbd_pool_name']);
154+
const sizeEl = fixture.debugElement.query(By.css('#size')).nativeElement;
155+
expect(sizeEl.value).toBe('1');
156+
const unitEl = fixture.debugElement.query(By.css('#unit')).nativeElement;
157+
expect(unitEl.value).toBe('GiB');
158+
});
159+
it('should not show namesapce count ', () => {
160+
const nsCountEl = fixture.debugElement.query(By.css('#namespace-count'));
161+
expect(nsCountEl).toBeFalsy();
162+
});
163+
it('should give error with no change in image size', () => {
164+
component.onSubmit();
165+
expect(component.invalidSizeError).toBe(true);
166+
fixture.detectChanges();
167+
const imageSizeInvalidEL = fixture.debugElement.query(By.css('#image-size-invalid'));
168+
expect(imageSizeInvalidEL).toBeTruthy();
169+
});
170+
it('should give error when size less than previous (1 GB) provided', () => {
171+
form = component.nsForm;
172+
formHelper = new FormHelper(form);
173+
formHelper.setValue('unit', 'MiB');
174+
component.onSubmit();
175+
expect(component.invalidSizeError).toBe(true);
176+
fixture.detectChanges();
177+
const imageSizeInvalidEL = fixture.debugElement.query(By.css('#image-size-invalid'))
178+
.nativeElement;
179+
expect(imageSizeInvalidEL).toBeTruthy();
180+
});
181+
it('should have edited namespace successfully', () => {
182+
component.ngOnInit();
183+
form = component.nsForm;
184+
formHelper = new FormHelper(form);
185+
formHelper.setValue('image_size', 2);
186+
component.onSubmit();
187+
expect(nvmeofService.updateNamespace).toHaveBeenCalledTimes(1);
188+
expect(nvmeofService.updateNamespace).toHaveBeenCalledWith(MOCK_SUBSYSTEM, MOCK_NSID, {
189+
gw_group: MOCK_GROUP,
190+
rbd_image_size: 2147483648
191+
});
192+
});
193+
});
96194
});

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-form/nvmeof-namespaces-form.component.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,21 @@ export class NvmeofNamespacesFormComponent implements OnInit {
8787
.subscribe((res: NvmeofSubsystemNamespace) => {
8888
const convertedSize = this.dimlessBinaryPipe.transform(res.rbd_image_size).split(' ');
8989
this.currentBytes = res.rbd_image_size;
90-
this.nsForm.get('image').setValue(res.rbd_image_name);
9190
this.nsForm.get('pool').setValue(res.rbd_pool_name);
9291
this.nsForm.get('unit').setValue(convertedSize[1]);
9392
this.nsForm.get('image_size').setValue(convertedSize[0]);
9493
this.nsForm.get('image_size').addValidators(Validators.required);
95-
this.nsForm.get('image').disable();
9694
this.nsForm.get('pool').disable();
9795
});
9896
}
9997

10098
initForCreate() {
10199
this.poolService.getList().subscribe((resp: Pool[]) => {
102100
this.rbdPools = resp.filter(this.rbdService.isRBDPool);
101+
if (this.rbdPools?.length) {
102+
this.nsForm.get('pool').setValue(this.rbdPools[0].pool_name);
103+
}
103104
});
104-
if (this.rbdPools?.length) {
105-
this.nsForm.get('pool').setValue(this.rbdPools[0].pool_name);
106-
}
107105
}
108106

109107
ngOnInit() {

src/pybind/mgr/dashboard/frontend/src/testing/activated-route-stub.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,33 @@ import { ActivatedRoute } from '@angular/router';
33
import { ReplaySubject } from 'rxjs';
44

55
/**
6-
* An ActivateRoute test double with a `params` observable.
6+
* An ActivateRoute test double with a `params` and `queryParams` observable.
77
* Use the `setParams()` method to add the next `params` value.
8+
* Use the `setQueryParams()` method to add the next `params` value.
89
*/
910
export class ActivatedRouteStub extends ActivatedRoute {
1011
// Use a ReplaySubject to share previous values with subscribers
1112
// and pump new values into the `params` observable
12-
private subject = new ReplaySubject<object>();
13+
private paramSubject = new ReplaySubject<object>();
14+
private queryParamSubject = new ReplaySubject<object>();
1315

14-
constructor(initialParams?: object) {
16+
constructor(initialParams?: object, initialQueryParams?: object) {
1517
super();
1618
this.setParams(initialParams);
19+
this.setQueryParams(initialQueryParams);
1720
}
1821

1922
/** The mock params observable */
20-
readonly params = this.subject.asObservable();
23+
readonly params = this.paramSubject.asObservable();
24+
/** The mock queryParams observable */
25+
readonly queryParams = this.queryParamSubject.asObservable();
2126

2227
/** Set the params observables's next value */
2328
setParams(params?: object) {
24-
this.subject.next(params);
29+
this.paramSubject.next(params);
30+
}
31+
/** Set the queryParams observables's next value */
32+
setQueryParams(queryParams?: object) {
33+
this.queryParamSubject.next(queryParams);
2534
}
2635
}

0 commit comments

Comments
 (0)