Skip to content

Commit 5f22a49

Browse files
authored
Merge pull request ceph#56282 from ivoalmeida/snapshot-schedule-repeat-frequency-validation
mgr/dashboard: snapshot schedule repeat frequency validation Reviewed-by: Pedro Gonzalez Gomez <[email protected]> Reviewed-by: Ankush Behl <[email protected]> Reviewed-by: Milind Changire <[email protected]> Reviewed-by: Nizamudeen A <[email protected]>
2 parents b986965 + 92b40f9 commit 5f22a49

File tree

3 files changed

+111
-83
lines changed

3 files changed

+111
-83
lines changed

src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.spec.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { HttpClientTestingModule } from '@angular/common/http/testing';
2-
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import {
3+
ComponentFixture,
4+
TestBed,
5+
discardPeriodicTasks,
6+
fakeAsync,
7+
tick
8+
} from '@angular/core/testing';
39

410
import { CephfsSnapshotscheduleFormComponent } from './cephfs-snapshotschedule-form.component';
511
import {
@@ -13,12 +19,12 @@ import { RouterTestingModule } from '@angular/router/testing';
1319
import { ReactiveFormsModule } from '@angular/forms';
1420
import { FormHelper, configureTestBed } from '~/testing/unit-test-helper';
1521
import { CephfsSnapshotScheduleService } from '~/app/shared/api/cephfs-snapshot-schedule.service';
22+
import { of } from 'rxjs';
1623

1724
describe('CephfsSnapshotscheduleFormComponent', () => {
1825
let component: CephfsSnapshotscheduleFormComponent;
1926
let fixture: ComponentFixture<CephfsSnapshotscheduleFormComponent>;
2027
let formHelper: FormHelper;
21-
let createSpy: jasmine.Spy;
2228

2329
configureTestBed({
2430
declarations: [CephfsSnapshotscheduleFormComponent],
@@ -40,7 +46,6 @@ describe('CephfsSnapshotscheduleFormComponent', () => {
4046
component.fsName = 'test_fs';
4147
component.ngOnInit();
4248
formHelper = new FormHelper(component.snapScheduleForm);
43-
createSpy = spyOn(TestBed.inject(CephfsSnapshotScheduleService), 'create').and.stub();
4449
fixture.detectChanges();
4550
});
4651

@@ -53,7 +58,12 @@ describe('CephfsSnapshotscheduleFormComponent', () => {
5358
expect(nativeEl.querySelector('cd-modal')).not.toBe(null);
5459
});
5560

56-
it('should submit the form', () => {
61+
it('should submit the form', fakeAsync(() => {
62+
const createSpy = spyOn(TestBed.inject(CephfsSnapshotScheduleService), 'create').and.stub();
63+
const checkScheduleExistsSpy = spyOn(
64+
TestBed.inject(CephfsSnapshotScheduleService),
65+
'checkScheduleExists'
66+
).and.returnValue(of(false));
5767
const input = {
5868
directory: '/test',
5969
startDate: {
@@ -73,7 +83,10 @@ describe('CephfsSnapshotscheduleFormComponent', () => {
7383
formHelper.setMultipleValues(input);
7484
component.snapScheduleForm.get('directory').setValue('/test');
7585
component.submit();
86+
tick(400);
7687

88+
expect(checkScheduleExistsSpy).toHaveBeenCalled();
7789
expect(createSpy).toHaveBeenCalled();
78-
});
90+
discardPeriodicTasks();
91+
}));
7992
});

src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.ts

Lines changed: 88 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ export class CephfsSnapshotscheduleFormComponent extends CdForm implements OnIni
115115
this.subvolume = value?.split?.('/')?.[3];
116116
}),
117117
filter(() => !!this.subvolume && !!this.subvolumeGroup),
118+
tap(() => {
119+
this.isSubvolume = !!this.subvolume && !!this.subvolumeGroup;
120+
this.snapScheduleForm.get('repeatFrequency').setErrors(null);
121+
}),
118122
mergeMap(() =>
119123
this.subvolumeService
120124
.exists(
@@ -282,84 +286,92 @@ export class CephfsSnapshotscheduleFormComponent extends CdForm implements OnIni
282286
}
283287

284288
submit() {
285-
if (this.snapScheduleForm.invalid) {
286-
this.snapScheduleForm.setErrors({ cdSubmitButton: true });
287-
return;
288-
}
289-
290-
const values = this.snapScheduleForm.value as SnapshotScheduleFormValue;
291-
292-
if (this.isEdit) {
293-
const retentionPoliciesToAdd = (this.snapScheduleForm.get(
294-
'retentionPolicies'
295-
) as FormArray).controls
296-
?.filter(
297-
(ctrl) =>
298-
!ctrl.get('retentionInterval').disabled && !ctrl.get('retentionFrequency').disabled
299-
)
300-
.map((ctrl) => ({
301-
retentionInterval: ctrl.get('retentionInterval').value,
302-
retentionFrequency: ctrl.get('retentionFrequency').value
303-
}));
304-
305-
const updateObj = {
306-
fs: this.fsName,
307-
path: this.path,
308-
subvol: this.subvol,
309-
group: this.group,
310-
retention_to_add: this.parseRetentionPolicies(retentionPoliciesToAdd) || null,
311-
retention_to_remove: this.parseRetentionPolicies(this.retentionPoliciesToRemove) || null
312-
};
313-
314-
this.taskWrapper
315-
.wrapTaskAroundCall({
316-
task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.EDIT, {
317-
path: this.path
318-
}),
319-
call: this.snapScheduleService.update(updateObj)
320-
})
321-
.subscribe({
322-
error: () => {
323-
this.snapScheduleForm.setErrors({ cdSubmitButton: true });
324-
},
325-
complete: () => {
326-
this.activeModal.close();
289+
this.validateSchedule()(this.snapScheduleForm).subscribe({
290+
next: () => {
291+
if (this.snapScheduleForm.invalid) {
292+
this.snapScheduleForm.setErrors({ cdSubmitButton: true });
293+
return;
294+
}
295+
296+
const values = this.snapScheduleForm.value as SnapshotScheduleFormValue;
297+
298+
if (this.isEdit) {
299+
const retentionPoliciesToAdd = (this.snapScheduleForm.get(
300+
'retentionPolicies'
301+
) as FormArray).controls
302+
?.filter(
303+
(ctrl) =>
304+
!ctrl.get('retentionInterval').disabled && !ctrl.get('retentionFrequency').disabled
305+
)
306+
.map((ctrl) => ({
307+
retentionInterval: ctrl.get('retentionInterval').value,
308+
retentionFrequency: ctrl.get('retentionFrequency').value
309+
}));
310+
311+
const updateObj = {
312+
fs: this.fsName,
313+
path: this.path,
314+
subvol: this.subvol,
315+
group: this.group,
316+
retention_to_add: this.parseRetentionPolicies(retentionPoliciesToAdd) || null,
317+
retention_to_remove: this.parseRetentionPolicies(this.retentionPoliciesToRemove) || null
318+
};
319+
320+
this.taskWrapper
321+
.wrapTaskAroundCall({
322+
task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.EDIT, {
323+
path: this.path
324+
}),
325+
call: this.snapScheduleService.update(updateObj)
326+
})
327+
.subscribe({
328+
error: () => {
329+
this.snapScheduleForm.setErrors({ cdSubmitButton: true });
330+
},
331+
complete: () => {
332+
this.activeModal.close();
333+
}
334+
});
335+
} else {
336+
const snapScheduleObj = {
337+
fs: this.fsName,
338+
path: values.directory,
339+
snap_schedule: this.parseSchedule(values?.repeatInterval, values?.repeatFrequency),
340+
start: this.parseDatetime(values?.startDate, values?.startTime)
341+
};
342+
343+
const retentionPoliciesValues = this.parseRetentionPolicies(values?.retentionPolicies);
344+
345+
if (retentionPoliciesValues) {
346+
snapScheduleObj['retention_policy'] = retentionPoliciesValues;
327347
}
328-
});
329-
} else {
330-
const snapScheduleObj = {
331-
fs: this.fsName,
332-
path: values.directory,
333-
snap_schedule: this.parseSchedule(values?.repeatInterval, values?.repeatFrequency),
334-
start: this.parseDatetime(values?.startDate, values?.startTime)
335-
};
336348

337-
const retentionPoliciesValues = this.parseRetentionPolicies(values?.retentionPolicies);
338-
339-
if (retentionPoliciesValues) snapScheduleObj['retention_policy'] = retentionPoliciesValues;
349+
if (this.isSubvolume) {
350+
snapScheduleObj['subvol'] = this.subvolume;
351+
}
340352

341-
if (this.isSubvolume) snapScheduleObj['subvol'] = this.subvolume;
353+
if (this.isSubvolume && !this.isDefaultSubvolumeGroup) {
354+
snapScheduleObj['group'] = this.subvolumeGroup;
355+
}
342356

343-
if (this.isSubvolume && !this.isDefaultSubvolumeGroup) {
344-
snapScheduleObj['group'] = this.subvolumeGroup;
357+
this.taskWrapper
358+
.wrapTaskAroundCall({
359+
task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.CREATE, {
360+
path: snapScheduleObj.path
361+
}),
362+
call: this.snapScheduleService.create(snapScheduleObj)
363+
})
364+
.subscribe({
365+
error: () => {
366+
this.snapScheduleForm.setErrors({ cdSubmitButton: true });
367+
},
368+
complete: () => {
369+
this.activeModal.close();
370+
}
371+
});
372+
}
345373
}
346-
347-
this.taskWrapper
348-
.wrapTaskAroundCall({
349-
task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.CREATE, {
350-
path: snapScheduleObj.path
351-
}),
352-
call: this.snapScheduleService.create(snapScheduleObj)
353-
})
354-
.subscribe({
355-
error: () => {
356-
this.snapScheduleForm.setErrors({ cdSubmitButton: true });
357-
},
358-
complete: () => {
359-
this.activeModal.close();
360-
}
361-
});
362-
}
374+
});
363375
}
364376

365377
validateSchedule() {
@@ -379,11 +391,13 @@ export class CephfsSnapshotscheduleFormComponent extends CdForm implements OnIni
379391
directory?.value,
380392
this.fsName,
381393
repeatInterval?.value,
382-
repeatFrequency?.value
394+
repeatFrequency?.value,
395+
this.isSubvolume
383396
)
384397
.pipe(
385398
map((exists: boolean) => {
386399
if (exists) {
400+
repeatFrequency?.markAsDirty();
387401
repeatFrequency?.setErrors({ notUnique: true }, { emitEvent: true });
388402
} else {
389403
repeatFrequency?.setErrors(null);

src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,14 @@ export class CephfsSnapshotScheduleService {
7373
path: string,
7474
fs: string,
7575
interval: number,
76-
frequency: RepeatFrequency
76+
frequency: RepeatFrequency,
77+
isSubvolume = false
7778
): Observable<boolean> {
7879
return this.getSnapshotScheduleList(path, fs, false).pipe(
7980
map((response) => {
80-
const index = response.findIndex(
81-
(x) => x.path === path && x.schedule === `${interval}${frequency}`
82-
);
81+
const index = response
82+
.filter((x) => (isSubvolume ? x.path.startsWith(path) : x.path === path))
83+
.findIndex((x) => x.schedule === `${interval}${frequency}`);
8384
return index > -1;
8485
}),
8586
catchError(() => {

0 commit comments

Comments
 (0)