From: Nizamudeen A Date: Wed, 27 Dec 2023 09:14:53 +0000 (+0530) Subject: mgr/dashboard: subvolume snapshot creation form X-Git-Tag: v19.1.0~535^2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=f5d1b7df2ed090b45dd74634e3c92d4df2c4017c;p=ceph.git mgr/dashboard: subvolume snapshot creation form Fixes: https://tracker.ceph.com/issues/63934 Signed-off-by: Nizamudeen A --- diff --git a/src/pybind/mgr/dashboard/controllers/cephfs.py b/src/pybind/mgr/dashboard/controllers/cephfs.py index 61b31eb809fd2..712efe11b0ddc 100644 --- a/src/pybind/mgr/dashboard/controllers/cephfs.py +++ b/src/pybind/mgr/dashboard/controllers/cephfs.py @@ -864,6 +864,33 @@ class CephFSSubvolumeSnapshots(RESTController): snapshot['info'] = json.loads(out) return snapshots + @RESTController.Resource('GET') + def info(self, vol_name: str, subvol_name: str, snap_name: str, group_name: str = ''): + params = {'vol_name': vol_name, 'sub_name': subvol_name, 'snap_name': snap_name} + if group_name: + params['group_name'] = group_name + error_code, out, err = mgr.remote('volumes', '_cmd_fs_subvolume_snapshot_info', None, + params) + if error_code != 0: + raise DashboardException( + f'Failed to get info for subvolume snapshot {snap_name}: {err}' + ) + return json.loads(out) + + def create(self, vol_name: str, subvol_name: str, snap_name: str, group_name=''): + params = {'vol_name': vol_name, 'sub_name': subvol_name, 'snap_name': snap_name} + if group_name: + params['group_name'] = group_name + + error_code, _, err = mgr.remote('volumes', '_cmd_fs_subvolume_snapshot_create', None, + params) + + if error_code != 0: + raise DashboardException( + f'Failed to create subvolume snapshot {snap_name}: {err}' + ) + return f'Subvolume snapshot {snap_name} created successfully' + @APIRouter('/cephfs/snaphost/schedule', Scope.CEPHFS) @APIDoc("Cephfs Snapshot Scheduling API", "CephFSSnapshotSchedule") diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.html new file mode 100644 index 0000000000000..867ed1bbfc168 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.html @@ -0,0 +1,98 @@ + + {{ action | titlecase }} {{ resource | upperFirst }} + + +
+ + + +
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.spec.ts new file mode 100644 index 0000000000000..a6eb923cdb2d9 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.spec.ts @@ -0,0 +1,41 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapshots-form.component'; +import { configureTestBed } from '~/testing/unit-test-helper'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { SharedModule } from '~/app/shared/shared.module'; +import { ToastrModule } from 'ngx-toastr'; +import { ReactiveFormsModule } from '@angular/forms'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +describe('CephfsSubvolumeSnapshotsFormComponent', () => { + let component: CephfsSubvolumeSnapshotsFormComponent; + let fixture: ComponentFixture; + + configureTestBed({ + declarations: [CephfsSubvolumeSnapshotsFormComponent], + providers: [NgbActiveModal], + imports: [ + SharedModule, + ToastrModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + RouterTestingModule + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CephfsSubvolumeSnapshotsFormComponent); + component = fixture.componentInstance; + component.fsName = 'test_volume'; + component.subVolumeName = 'test_subvolume'; + component.subVolumeGroupName = 'test_subvolume_group'; + component.ngOnInit(); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.ts new file mode 100644 index 0000000000000..92757d334acd7 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component.ts @@ -0,0 +1,127 @@ +import { Component, OnInit } from '@angular/core'; +import { FormControl, Validators } from '@angular/forms'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import moment from 'moment'; +import { Observable } from 'rxjs'; +import { CephfsSubvolumeService } from '~/app/shared/api/cephfs-subvolume.service'; +import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; +import { CdForm } from '~/app/shared/forms/cd-form'; +import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; +import { CdValidators } from '~/app/shared/forms/cd-validators'; +import { CephfsSubvolume } from '~/app/shared/models/cephfs-subvolume.model'; +import { FinishedTask } from '~/app/shared/models/finished-task'; +import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; + +@Component({ + selector: 'cd-cephfs-subvolume-snapshots-form', + templateUrl: './cephfs-subvolume-snapshots-form.component.html', + styleUrls: ['./cephfs-subvolume-snapshots-form.component.scss'] +}) +export class CephfsSubvolumeSnapshotsFormComponent extends CdForm implements OnInit { + fsName: string; + subVolumeName: string; + subVolumeGroupName: string; + subVolumeGroups: string[]; + + isEdit = false; + + snapshotForm: CdFormGroup; + + action: string; + resource: string; + + subVolumes$: Observable; + + constructor( + public activeModal: NgbActiveModal, + private actionLabels: ActionLabelsI18n, + private taskWrapper: TaskWrapperService, + private cephFsSubvolumeService: CephfsSubvolumeService + ) { + super(); + this.resource = $localize`snapshot`; + this.action = this.actionLabels.CREATE; + } + + ngOnInit(): void { + this.createForm(); + + this.subVolumes$ = this.cephFsSubvolumeService.get(this.fsName, this.subVolumeGroupName, false); + this.loadingReady(); + } + + createForm() { + this.snapshotForm = new CdFormGroup({ + snapshotName: new FormControl(moment().toISOString(true), { + validators: [Validators.required], + asyncValidators: [ + CdValidators.unique( + this.cephFsSubvolumeService.snapshotExists, + this.cephFsSubvolumeService, + null, + null, + this.fsName, + this.subVolumeName, + this.subVolumeGroupName + ) + ] + }), + volumeName: new FormControl({ value: this.fsName, disabled: true }), + subVolumeName: new FormControl(this.subVolumeName), + subvolumeGroupName: new FormControl(this.subVolumeGroupName) + }); + } + + onSelectionChange(groupName: string) { + this.subVolumeGroupName = groupName; + this.subVolumes$ = this.cephFsSubvolumeService.get(this.fsName, this.subVolumeGroupName, false); + this.subVolumes$.subscribe((subVolumes) => { + this.subVolumeName = subVolumes[0].name; + this.snapshotForm.get('subVolumeName').setValue(this.subVolumeName); + + this.resetValidators(); + }); + } + + resetValidators(subVolumeName?: string) { + this.subVolumeName = subVolumeName; + this.snapshotForm + .get('snapshotName') + .setAsyncValidators( + CdValidators.unique( + this.cephFsSubvolumeService.snapshotExists, + this.cephFsSubvolumeService, + null, + null, + this.fsName, + this.subVolumeName, + this.subVolumeGroupName + ) + ); + this.snapshotForm.get('snapshotName').updateValueAndValidity(); + } + + submit() { + const snapshotName = this.snapshotForm.getValue('snapshotName'); + const subVolumeName = this.snapshotForm.getValue('subVolumeName'); + const subVolumeGroupName = this.snapshotForm.getValue('subvolumeGroupName'); + const volumeName = this.snapshotForm.getValue('volumeName'); + + this.taskWrapper + .wrapTaskAroundCall({ + task: new FinishedTask('cephfs/subvolume/snapshot/' + URLVerbs.CREATE, { + snapshotName: snapshotName + }), + call: this.cephFsSubvolumeService.createSnapshot( + volumeName, + snapshotName, + subVolumeName, + subVolumeGroupName + ) + }) + .subscribe({ + error: () => this.snapshotForm.setErrors({ cdSubmitButton: true }), + complete: () => this.activeModal.close() + }); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.html index de31172365241..190072027bcff 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.html @@ -26,7 +26,18 @@ [columns]="columns" selectionType="single" [hasDetails]="false" - (fetchData)="fetchData()"> + (fetchData)="fetchData()" + (updateSelection)="updateSelection($event)"> + +
+ + +
+ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.ts index 251314c3e86a8..9970d59888798 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component.ts @@ -3,10 +3,19 @@ import { BehaviorSubject, Observable, forkJoin, of } from 'rxjs'; import { catchError, shareReplay, switchMap, tap } from 'rxjs/operators'; import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service'; import { CephfsSubvolumeService } from '~/app/shared/api/cephfs-subvolume.service'; +import { ActionLabelsI18n } from '~/app/shared/constants/app.constants'; import { CellTemplate } from '~/app/shared/enum/cell-template.enum'; +import { Icons } from '~/app/shared/enum/icons.enum'; +import { CdTableAction } from '~/app/shared/models/cd-table-action'; import { CdTableColumn } from '~/app/shared/models/cd-table-column'; import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context'; import { CephfsSubvolume, SubvolumeSnapshot } from '~/app/shared/models/cephfs-subvolume.model'; +import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component'; +import { ModalService } from '~/app/shared/services/modal.service'; +import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; +import { Permissions } from '~/app/shared/models/permissions'; +import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; +import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe'; @Component({ selector: 'cd-cephfs-subvolume-snapshots-list', @@ -18,6 +27,9 @@ export class CephfsSubvolumeSnapshotsListComponent implements OnInit, OnChanges context: CdTableFetchDataContext; columns: CdTableColumn[] = []; + tableActions: CdTableAction[]; + selection = new CdTableSelection(); + permissions: Permissions; subVolumes$: Observable; snapshots$: Observable; @@ -37,8 +49,14 @@ export class CephfsSubvolumeSnapshotsListComponent implements OnInit, OnChanges constructor( private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService, - private cephfsSubvolumeService: CephfsSubvolumeService - ) {} + private cephfsSubvolumeService: CephfsSubvolumeService, + private actionLabels: ActionLabelsI18n, + private modalService: ModalService, + private authStorageService: AuthStorageService, + private cdDatePipe: CdDatePipe + ) { + this.permissions = this.authStorageService.getPermissions(); + } ngOnInit(): void { this.columns = [ @@ -51,7 +69,7 @@ export class CephfsSubvolumeSnapshotsListComponent implements OnInit, OnChanges name: $localize`Created`, prop: 'info.created_at', flexGrow: 1, - cellTransformation: CellTemplate.timeAgo + pipe: this.cdDatePipe }, { name: $localize`Pending Clones`, @@ -67,6 +85,15 @@ export class CephfsSubvolumeSnapshotsListComponent implements OnInit, OnChanges } ]; + this.tableActions = [ + { + name: this.actionLabels.CREATE, + permission: 'create', + icon: Icons.add, + click: () => this.openModal() + } + ]; + this.cephfsSubvolumeGroupService .get(this.fsName) .pipe( @@ -145,4 +172,22 @@ export class CephfsSubvolumeSnapshotsListComponent implements OnInit, OnChanges fetchData() { this.snapshotSubject.next([]); } + + openModal(edit = false) { + this.modalService.show( + CephfsSubvolumeSnapshotsFormComponent, + { + fsName: this.fsName, + subVolumeName: this.activeSubVolumeName, + subVolumeGroupName: this.activeGroupName, + subVolumeGroups: this.subvolumeGroupList, + isEdit: edit + }, + { size: 'lg' } + ); + } + + updateSelection(selection: CdTableSelection) { + this.selection = selection; + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts index 53544ccd9ed40..41451d9e3c0cc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts @@ -22,6 +22,7 @@ import { CephfsSubvolumegroupFormComponent } from './cephfs-subvolumegroup-form/ import { CephfsSubvolumeSnapshotsListComponent } from './cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-list.component'; import { CephfsSnapshotscheduleListComponent } from './cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component'; import { DataTableModule } from '../../shared/datatable/datatable.module'; +import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component'; @NgModule({ imports: [ @@ -51,7 +52,8 @@ import { DataTableModule } from '../../shared/datatable/datatable.module'; CephfsSubvolumeGroupComponent, CephfsSubvolumegroupFormComponent, CephfsSubvolumeSnapshotsListComponent, - CephfsSnapshotscheduleListComponent + CephfsSnapshotscheduleListComponent, + CephfsSubvolumeSnapshotsFormComponent ] }) export class CephfsModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-subvolume.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-subvolume.service.spec.ts index 2e8448ff1a22d..10ef5ea00f73a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-subvolume.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-subvolume.service.spec.ts @@ -48,4 +48,10 @@ describe('CephfsSubvolumeService', () => { ); expect(req.request.method).toBe('GET'); }); + + it('should call createSnapshot', () => { + service.createSnapshot('testFS', 'testSnap', 'testSubvol').subscribe(); + const req = httpTesting.expectOne('api/cephfs/subvolume/snapshot/'); + expect(req.request.method).toBe('POST'); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-subvolume.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-subvolume.service.ts index d76523aafd2ad..1995fd293bae0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-subvolume.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-subvolume.service.ts @@ -117,4 +117,48 @@ export class CephfsSubvolumeService { } ); } + + getSnapshotInfo(snapshotName: string, fsName: string, subVolumeName: string, groupName = '') { + return this.http.get(`${this.baseURL}/snapshot/${fsName}/${subVolumeName}/info`, { + params: { + snap_name: snapshotName, + group_name: groupName + } + }); + } + + snapshotExists( + fsName: string, + snapshotName: string, + subVolumeName: string, + groupName: string = '' + ): Observable { + return this.getSnapshotInfo(fsName, snapshotName, subVolumeName, groupName).pipe( + mapTo(true), + catchError((error: Event) => { + if (_.isFunction(error.preventDefault)) { + error.preventDefault(); + } + return of(false); + }) + ); + } + + createSnapshot( + fsName: string, + snapshotName: string, + subVolumeName: string, + groupName: string = '' + ) { + return this.http.post( + `${this.baseURL}/snapshot/`, + { + vol_name: fsName, + subvol_name: subVolumeName, + snap_name: snapshotName, + group_name: groupName + }, + { observe: 'response' } + ); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts index bea426724e073..602a11e7343cd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.ts @@ -18,7 +18,7 @@ export function isEmptyInputValue(value: any): boolean { return value == null || value.length === 0; } -export type existsServiceFn = (value: any, args?: any) => Observable; +export type existsServiceFn = (value: any, ...args: any[]) => Observable; export class CdValidators { /** @@ -359,7 +359,7 @@ export class CdValidators { serviceFnThis: any = null, usernameFn?: Function, uidField = false, - extraArgs = '' + ...extraArgs: any[] ): AsyncValidatorFn { let uName: string; return (control: AbstractControl): Observable => { @@ -378,7 +378,7 @@ export class CdValidators { } return observableTimer().pipe( - switchMapTo(serviceFn.call(serviceFnThis, uName, extraArgs)), + switchMapTo(serviceFn.call(serviceFnThis, uName, ...extraArgs)), map((resp: boolean) => { if (!resp) { return null; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.spec.ts index b67ed62c8a6c7..b711bdbb1cefd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.spec.ts @@ -1,15 +1,12 @@ -import { DatePipe } from '@angular/common'; - import moment from 'moment'; import { CdDatePipe } from './cd-date.pipe'; describe('CdDatePipe', () => { - const datePipe = new DatePipe('en-US'); - let pipe = new CdDatePipe(datePipe); + let pipe = new CdDatePipe(); it('create an instance', () => { - pipe = new CdDatePipe(datePipe); + pipe = new CdDatePipe(); expect(pipe).toBeTruthy(); }); @@ -18,7 +15,12 @@ describe('CdDatePipe', () => { }); it('transforms with some date', () => { - const result = moment(1527085564486).format('M/D/YY LTS'); + const result = moment + .parseZone(moment.unix(1527085564486)) + .utc() + .utcOffset(moment().utcOffset()) + .local() + .format('D/M/YY hh:mm A'); expect(pipe.transform(1527085564486)).toBe(result); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.ts index 911f320410f4b..887d8d6bfb98c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/cd-date.pipe.ts @@ -1,20 +1,30 @@ -import { DatePipe } from '@angular/common'; import { Pipe, PipeTransform } from '@angular/core'; +import _ from 'lodash'; +import moment from 'moment'; @Pipe({ name: 'cdDate' }) export class CdDatePipe implements PipeTransform { - constructor(private datePipe: DatePipe) {} + constructor() {} transform(value: any): any { if (value === null || value === '') { return ''; } - return ( - this.datePipe.transform(value, 'shortDate') + - ' ' + - this.datePipe.transform(value, 'mediumTime') - ); + let date: string; + const offset = moment().utcOffset(); + if (_.isNumber(value)) { + date = moment + .parseZone(moment.unix(value)) + .utc() + .utcOffset(offset) + .local() + .format('D/M/YY hh:mm A'); + } else { + value = value?.replace('Z', ''); + date = moment.parseZone(value).utc().utcOffset(offset).local().format('D/M/YY hh:mm A'); + } + return date; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts index f6969c2e8e1b4..c1165d318a364 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts @@ -379,6 +379,10 @@ export class TaskMessageService { ), 'cephfs/subvolume/group/remove': this.newTaskMessage(this.commonOperations.remove, (metadata) => this.subvolumegroup(metadata) + ), + 'cephfs/subvolume/snapshot/create': this.newTaskMessage( + this.commonOperations.create, + (metadata) => this.snapshot(metadata) ) }; @@ -447,6 +451,10 @@ export class TaskMessageService { return $localize`subvolume group '${metadata.subvolumegroupName}'`; } + snapshot(metadata: any) { + return $localize`snapshot '${metadata.snapshotName}'`; + } + crudMessageId(id: string) { return $localize`${id}`; } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 0ed83aab6c8ec..6129321c7df20 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -2019,6 +2019,52 @@ paths: - jwt: [] tags: - CephfsSubvolumeGroup + /api/cephfs/subvolume/snapshot: + post: + parameters: [] + requestBody: + content: + application/json: + schema: + properties: + group_name: + default: '' + type: string + snap_name: + type: string + subvol_name: + type: string + vol_name: + type: string + required: + - vol_name + - subvol_name + - snap_name + type: object + responses: + '201': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Resource created. + '202': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Operation is still executing. Please check the task queue. + '400': + description: Operation exception. Please check the response body for details. + '401': + description: Unauthenticated access. Please login first. + '403': + description: Unauthorized access. Please check your permissions. + '500': + description: Unexpected error. Please check the response body for the stack + trace. + security: + - jwt: [] + tags: + - CephfsSubvolumeSnapshot /api/cephfs/subvolume/snapshot/{vol_name}/{subvol_name}: get: parameters: @@ -2061,6 +2107,48 @@ paths: - jwt: [] tags: - CephfsSubvolumeSnapshot + /api/cephfs/subvolume/snapshot/{vol_name}/{subvol_name}/info: + get: + parameters: + - in: path + name: vol_name + required: true + schema: + type: string + - in: path + name: subvol_name + required: true + schema: + type: string + - in: query + name: snap_name + required: true + schema: + type: string + - default: '' + in: query + name: group_name + schema: + type: string + responses: + '200': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: OK + '400': + description: Operation exception. Please check the response body for details. + '401': + description: Unauthenticated access. Please login first. + '403': + description: Unauthorized access. Please check your permissions. + '500': + description: Unexpected error. Please check the response body for the stack + trace. + security: + - jwt: [] + tags: + - CephfsSubvolumeSnapshot /api/cephfs/subvolume/{vol_name}: delete: parameters: