From 80e1207f4b536fe6edbc81e61cbf951e135eba54 Mon Sep 17 00:00:00 2001 From: Ivo Almeida Date: Wed, 21 Feb 2024 13:02:19 +0000 Subject: [PATCH] mgr/dashboard: fix retention add for subvolume - Added parameters for subvolume and subvolume group when adding a new snap schedule. - Added call to remove retention policies when removing a snap schedule in case it is the last one with same path Fixes: https://tracker.ceph.com/issues/64524 Signed-off-by: Ivo Almeida --- .../mgr/dashboard/controllers/cephfs.py | 35 +++++++++++++++++-- .../cephfs-snapshotschedule-form.component.ts | 3 +- .../cephfs-snapshotschedule-list.component.ts | 13 ++++++- .../api/cephfs-snapshot-schedule.service.ts | 20 +++++++++-- src/pybind/mgr/dashboard/openapi.yaml | 5 +++ 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/cephfs.py b/src/pybind/mgr/dashboard/controllers/cephfs.py index 6410a73785ecb..4210746fbd1ff 100644 --- a/src/pybind/mgr/dashboard/controllers/cephfs.py +++ b/src/pybind/mgr/dashboard/controllers/cephfs.py @@ -1029,7 +1029,9 @@ class CephFSSnapshotSchedule(RESTController): path, retention_spec_or_period, retention_count, - fs) + fs, + subvol, + group) if error_code_retention != 0: raise DashboardException( f'Failed to add retention policy for path {path}: {err_retention}' @@ -1071,7 +1073,36 @@ class CephFSSnapshotSchedule(RESTController): @RESTController.Resource('DELETE') def delete_snapshot(self, fs: str, path: str, schedule: str, start: str, - subvol=None, group=None): + retention_policy=None, subvol=None, group=None): + if retention_policy: + # check if there are other snap schedules for this exact same path + error_code, out, err = mgr.remote('snap_schedule', 'snap_schedule_list', + path, False, fs, subvol, group, 'plain') + + if error_code != 0: + raise DashboardException( + f'Failed to get snapshot schedule list for path {path}: {err}' + ) + # only remove the retention policies if there no other snap schedules for this path + snapshot_schedule_list = out.split('\n') + if len(snapshot_schedule_list) <= 1: + retention_policies = retention_policy.split('|') + for retention in retention_policies: + retention_count = retention.split('-')[0] + retention_spec_or_period = retention.split('-')[1] + error_code, _, err = mgr.remote('snap_schedule', + 'snap_schedule_retention_rm', + path, + retention_spec_or_period, + retention_count, + fs, + subvol, + group) + if error_code != 0: + raise DashboardException( + f'Failed to remove retention policy for path {path}: {err}' + ) + # remove snap schedule error_code, _, err = mgr.remote('snap_schedule', 'snap_schedule_rm', path, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.ts index 0ef68f7d529c5..22fa33f0fe1cb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component.ts @@ -389,7 +389,8 @@ export class CephfsSnapshotscheduleFormComponent extends CdForm implements OnIni frm.get('directory').value, this.fsName, retentionList, - this.retentionPoliciesToRemove?.map?.((rp) => rp.retentionFrequency) || [] + this.retentionPoliciesToRemove?.map?.((rp) => rp.retentionFrequency) || [], + !!this.subvolume ) .pipe( map(({ exists, errorIndex }) => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts index f6f372dcffc4e..9a131a1e80b7c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts @@ -291,7 +291,17 @@ export class CephfsSnapshotscheduleListComponent } deleteSnapshotSchedule() { - const { path, start, fs, schedule, subvol, group } = this.selection.first(); + const { path, start, fs, schedule, subvol, group, retention } = this.selection.first(); + const retentionPolicy = retention + ?.split(/\s/gi) + ?.filter((r: string) => !!r) + ?.map((r: string) => { + const frequency = r.substring(r.length - 1); + const interval = r.substring(0, r.length - 1); + return `${interval}-${frequency}`; + }) + ?.join('|') + ?.toLocaleLowerCase(); this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { itemDescription: $localize`snapshot schedule`, @@ -305,6 +315,7 @@ export class CephfsSnapshotscheduleListComponent schedule, start, fs, + retentionPolicy, subvol, group }) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts index 93c04dc38ed95..ade935a9299a4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts @@ -48,10 +48,21 @@ export class CephfsSnapshotScheduleService { ); } - delete({ fs, path, schedule, start, subvol, group }: Record): Observable { + delete({ + fs, + path, + schedule, + start, + retentionPolicy, + subvol, + group + }: Record): Observable { let deleteUrl = `${this.baseURL}/snapshot/schedule/${fs}/${encodeURIComponent( path )}/delete_snapshot?schedule=${schedule}&start=${encodeURIComponent(start)}`; + if (retentionPolicy) { + deleteUrl += `&retention_policy=${retentionPolicy}`; + } if (subvol && group) { deleteUrl += `&subvol=${encodeURIComponent(subvol)}&group=${encodeURIComponent(group)}`; } @@ -81,13 +92,16 @@ export class CephfsSnapshotScheduleService { path: string, fs: string, retentionFrequencies: string[], - retentionFrequenciesRemoved: string[] = [] + retentionFrequenciesRemoved: string[] = [], + isSubvolume = false ): Observable<{ exists: boolean; errorIndex: number }> { return this.getSnapshotSchedule(path, fs, false).pipe( map((response) => { let errorIndex = -1; let exists = false; - const index = response.findIndex((x) => x.path === path); + const index = response.findIndex((x) => + isSubvolume ? x.path.startsWith(path) : x.path === path + ); const result = retentionFrequencies?.length ? intersection( Object.keys(response?.[index]?.retention).filter( diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index f3d4f3607f333..5244d4a983b26 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -2081,6 +2081,11 @@ paths: required: true schema: type: string + - allowEmptyValue: true + in: query + name: retention_policy + schema: + type: string - allowEmptyValue: true in: query name: subvol -- 2.39.5