Skip to content

Commit 0939122

Browse files
authored
Merge pull request ceph#58636 from afreen23/wip-nvmeof-namespace
mgr/dashboard: Add namespaces views in dashboard Reviewed-by: Ankush Behl <[email protected]> Reviewed-by: Nizamudeen A <[email protected]>
2 parents f0f3810 + b45eb55 commit 0939122

21 files changed

+876
-14
lines changed

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import { NvmeofTabsComponent } from './nvmeof-tabs/nvmeof-tabs.component';
4545
import { NvmeofSubsystemsFormComponent } from './nvmeof-subsystems-form/nvmeof-subsystems-form.component';
4646
import { NvmeofListenersFormComponent } from './nvmeof-listeners-form/nvmeof-listeners-form.component';
4747
import { NvmeofListenersListComponent } from './nvmeof-listeners-list/nvmeof-listeners-list.component';
48+
import { NvmeofNamespacesListComponent } from './nvmeof-namespaces-list/nvmeof-namespaces-list.component';
49+
import { NvmeofNamespacesFormComponent } from './nvmeof-namespaces-form/nvmeof-namespaces-form.component';
4850

4951
@NgModule({
5052
imports: [
@@ -91,7 +93,9 @@ import { NvmeofListenersListComponent } from './nvmeof-listeners-list/nvmeof-lis
9193
NvmeofTabsComponent,
9294
NvmeofSubsystemsFormComponent,
9395
NvmeofListenersFormComponent,
94-
NvmeofListenersListComponent
96+
NvmeofListenersListComponent,
97+
NvmeofNamespacesListComponent,
98+
NvmeofNamespacesFormComponent
9599
],
96100
exports: [RbdConfigurationListComponent, RbdConfigurationFormComponent]
97101
})
@@ -238,20 +242,32 @@ const routes: Routes = [
238242
component: NvmeofSubsystemsComponent,
239243
data: { breadcrumbs: 'Subsystems' },
240244
children: [
245+
// subsystems
241246
{ path: '', component: NvmeofSubsystemsComponent },
242247
{
243248
path: URLVerbs.CREATE,
244249
component: NvmeofSubsystemsFormComponent,
245250
outlet: 'modal'
246251
},
247252
{
248-
path: `${URLVerbs.EDIT}/:subsystem_nqn`,
253+
path: `${URLVerbs.EDIT}/:subsystem_nqn/:max_ns`,
249254
component: NvmeofSubsystemsFormComponent,
250255
outlet: 'modal'
251256
},
257+
// listeners
252258
{
253259
path: `${URLVerbs.CREATE}/:subsystem_nqn/listener`,
254-
component: NvmeofListenersFormComponent,
260+
component: NvmeofListenersFormComponent
261+
},
262+
// namespaces
263+
{
264+
path: `${URLVerbs.CREATE}/:subsystem_nqn/namespace`,
265+
component: NvmeofNamespacesFormComponent,
266+
outlet: 'modal'
267+
},
268+
{
269+
path: `${URLVerbs.EDIT}/:subsystem_nqn/namespace/:nsid`,
270+
component: NvmeofNamespacesFormComponent,
255271
outlet: 'modal'
256272
}
257273
]
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<cd-modal [pageURL]="pageURL"
2+
[modalRef]="activeModal">
3+
<span class="modal-title"
4+
i18n>{{ action | titlecase }} {{ resource | upperFirst }}</span>
5+
<ng-container class="modal-content">
6+
<form name="nsForm"
7+
#formDir="ngForm"
8+
[formGroup]="nsForm"
9+
novalidate>
10+
<div class="modal-body">
11+
<!-- Block Pool -->
12+
<div class="form-group row">
13+
<label class="cd-col-form-label"
14+
for="pool">
15+
<span [ngClass]="{'required': !edit}"
16+
i18n>Pool</span>
17+
</label>
18+
<div class="cd-col-form-input">
19+
<input *ngIf="edit"
20+
name="pool"
21+
class="form-control"
22+
type="text"
23+
formControlName="pool">
24+
<select *ngIf="!edit"
25+
id="pool"
26+
name="pool"
27+
class="form-select"
28+
formControlName="pool">
29+
<option *ngIf="rbdPools === null"
30+
[ngValue]="null"
31+
i18n>Loading...</option>
32+
<option *ngIf="rbdPools && rbdPools.length === 0"
33+
[ngValue]="null"
34+
i18n>-- No block pools available --</option>
35+
<option *ngIf="rbdPools && rbdPools.length > 0"
36+
[ngValue]="null"
37+
i18n>-- Select a pool --</option>
38+
<option *ngFor="let pool of rbdPools"
39+
[value]="pool.pool_name">{{ pool.pool_name }}</option>
40+
</select>
41+
<cd-help-text i18n>
42+
A RBD application-enabled pool where the image will be created.
43+
</cd-help-text>
44+
<span class="invalid-feedback"
45+
*ngIf="nsForm.showError('pool', formDir, 'required')"
46+
i18n>This field is required.</span>
47+
</div>
48+
</div>
49+
<!-- Image Name -->
50+
<div class="form-group row">
51+
<label class="cd-col-form-label"
52+
for="image">
53+
<span [ngClass]="{'required': !edit}"
54+
i18n>Image Name</span>
55+
</label>
56+
<div class="cd-col-form-input">
57+
<input name="image"
58+
class="form-control"
59+
type="text"
60+
formControlName="image">
61+
<span class="invalid-feedback"
62+
*ngIf="nsForm.showError('image', formDir, 'required')">
63+
<ng-container i18n>This field is required.</ng-container>
64+
</span>
65+
<span class="invalid-feedback"
66+
*ngIf="nsForm.showError('image', formDir, 'pattern')">
67+
<ng-container i18n>'/' and '@' are not allowed.</ng-container>
68+
</span>
69+
</div>
70+
</div>
71+
<!-- Image Size -->
72+
<div class="form-group row">
73+
<label class="cd-col-form-label"
74+
for="image_size">
75+
<span [ngClass]="{'required': edit}"
76+
i18n>Image Size</span>
77+
</label>
78+
<div class="cd-col-form-input">
79+
<div class="input-group">
80+
<input id="size"
81+
class="form-control"
82+
type="text"
83+
name="image_size"
84+
formControlName="image_size">
85+
<select id="unit"
86+
name="unit"
87+
class="form-input form-select"
88+
formControlName="unit">
89+
<option *ngFor="let u of units"
90+
[value]="u"
91+
i18n>{{ u }}</option>
92+
</select>
93+
<span class="invalid-feedback"
94+
*ngIf="nsForm.showError('image_size', formDir, 'pattern')">
95+
<ng-container i18n>Enter a positive integer.</ng-container>
96+
</span>
97+
<span class="invalid-feedback"
98+
*ngIf="edit && nsForm.showError('image_size', formDir, 'required')">
99+
<ng-container i18n>This field is required</ng-container>
100+
</span>
101+
<span class="invalid-feedback"
102+
*ngIf="edit && invalidSizeError">
103+
<ng-container i18n>Enter a value above than previous.</ng-container>
104+
</span>
105+
</div>
106+
</div>
107+
</div>
108+
</div>
109+
<div class="modal-footer">
110+
<div class="text-right">
111+
<cd-form-button-panel (submitActionEvent)="onSubmit()"
112+
[form]="nsForm"
113+
[submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"></cd-form-button-panel>
114+
</div>
115+
</div>
116+
</form>
117+
</ng-container>
118+
</cd-modal>

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

Whitespace-only changes.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { HttpClientTestingModule } from '@angular/common/http/testing';
2+
import { ReactiveFormsModule } from '@angular/forms';
3+
import { RouterTestingModule } from '@angular/router/testing';
4+
import { ComponentFixture, TestBed } from '@angular/core/testing';
5+
6+
import { ToastrModule } from 'ngx-toastr';
7+
8+
import { NgbActiveModal, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
9+
10+
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
11+
import { SharedModule } from '~/app/shared/shared.module';
12+
13+
import { NvmeofNamespacesFormComponent } from './nvmeof-namespaces-form.component';
14+
import { FormHelper } from '~/testing/unit-test-helper';
15+
import { NvmeofService } from '~/app/shared/api/nvmeof.service';
16+
17+
describe('NvmeofNamespacesFormComponent', () => {
18+
let component: NvmeofNamespacesFormComponent;
19+
let fixture: ComponentFixture<NvmeofNamespacesFormComponent>;
20+
let nvmeofService: NvmeofService;
21+
let form: CdFormGroup;
22+
let formHelper: FormHelper;
23+
const mockTimestamp = 1720693470789;
24+
const mockSubsystemNQN = 'nqn.2021-11.com.example:subsystem';
25+
26+
beforeEach(async () => {
27+
spyOn(Date, 'now').and.returnValue(mockTimestamp);
28+
await TestBed.configureTestingModule({
29+
declarations: [NvmeofNamespacesFormComponent],
30+
providers: [NgbActiveModal],
31+
imports: [
32+
HttpClientTestingModule,
33+
NgbTypeaheadModule,
34+
ReactiveFormsModule,
35+
RouterTestingModule,
36+
SharedModule,
37+
ToastrModule.forRoot()
38+
]
39+
}).compileComponents();
40+
41+
fixture = TestBed.createComponent(NvmeofNamespacesFormComponent);
42+
component = fixture.componentInstance;
43+
component.ngOnInit();
44+
form = component.nsForm;
45+
formHelper = new FormHelper(form);
46+
fixture.detectChanges();
47+
});
48+
49+
it('should create', () => {
50+
expect(component).toBeTruthy();
51+
});
52+
53+
describe('should test form', () => {
54+
beforeEach(() => {
55+
component.subsystemNQN = mockSubsystemNQN;
56+
nvmeofService = TestBed.inject(NvmeofService);
57+
spyOn(nvmeofService, 'createNamespace').and.stub();
58+
});
59+
60+
it('should be creating request correctly', () => {
61+
const image = 'nvme_ns_image:' + mockTimestamp;
62+
component.onSubmit();
63+
expect(nvmeofService.createNamespace).toHaveBeenCalledWith(mockSubsystemNQN, {
64+
rbd_image_name: image,
65+
rbd_pool: null,
66+
size: 1073741824
67+
});
68+
});
69+
70+
it('should give error on invalid image name', () => {
71+
formHelper.setValue('image', '/ghfhdlk;kd;@');
72+
component.onSubmit();
73+
formHelper.expectError('image', 'pattern');
74+
});
75+
76+
it('should give error on invalid image size', () => {
77+
formHelper.setValue('image_size', -56);
78+
component.onSubmit();
79+
formHelper.expectError('image_size', 'pattern');
80+
});
81+
82+
it('should give error on 0 image size', () => {
83+
formHelper.setValue('image_size', 0);
84+
component.onSubmit();
85+
formHelper.expectError('image_size', 'min');
86+
});
87+
});
88+
});

0 commit comments

Comments
 (0)