-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(storage) crud for custom volume snapshots
- removed snapshot volume type from volumes list filter. - clicking on volume name will redirect to instance detail page if it's a container or vm volume, redirect to image list page if it's a image volume and redirect to volume details if it's a custom volume. - added the snapshots column to the volumes list table. Number of snapshots per volume is calculated based on number of snapshot volumes that matches each volume name. - CTA for storage volume list table. For custom storage volumes, user can add snapshots or delete volume. For instance volumes, link to instance detail and for image volumes link to images list. Show CTA only on hover volume row. - Generalised snapshot form modal - Snapshot form modal will now be rendered using portal - Added storage volume snapshot api - Create instance and custom volume snapshots using the generalised snapshot form modal - Fixed issue with snapshot form modal retaining stale states after snapshot created, this was an issue with instance snapshot as well - Implemented snapshots tab for storage volume detail page (heavy reference to the snapshots tab for instnace detail page) - Code cleanup - Fixed issue with not able to sort by the snapshots column on the storage volume list table - Fixed issue with disabling snapshot creation if project is restricted - Fixed tooltip wording for add snapshot CTA button on volume list table - Fixes based on David's comments: - Error handling for instance and volume snapshot creation when project is restricted - Moved VolumeSnapshotsForm out of the generic component folder - Added tests for custom volume snapshot crud - More changes for David's review: - unique naming for instance and volume snapshot api methods - removed PortalModalBtn and created addition button components for readability - each file should contain one component Signed-off-by: Mason Hu <mason.hu@canonical.com>
- Loading branch information
Showing
56 changed files
with
2,815 additions
and
641 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { | ||
continueOrFinish, | ||
handleResponse, | ||
pushFailure, | ||
pushSuccess, | ||
} from "util/helpers"; | ||
import { LxdOperationResponse } from "types/operation"; | ||
import { LxdStorageVolume, LxdVolumeSnapshot } from "types/storage"; | ||
import { LxdApiResponse, LxdSyncResponse } from "types/apiResponse"; | ||
import { EventQueue } from "context/eventQueue"; | ||
|
||
export const createVolumeSnapshot = (args: { | ||
volume: LxdStorageVolume; | ||
name: string; | ||
expiresAt: string | null; | ||
}): Promise<LxdOperationResponse> => { | ||
const { volume, name, expiresAt } = args; | ||
return new Promise((resolve, reject) => { | ||
fetch( | ||
`/1.0/storage-pools/${volume.pool}/volumes/custom/${volume.name}/snapshots?project=${volume.project}`, | ||
{ | ||
method: "POST", | ||
body: JSON.stringify({ | ||
name, | ||
expires_at: expiresAt, | ||
}), | ||
}, | ||
) | ||
.then(handleResponse) | ||
.then(resolve) | ||
.catch(reject); | ||
}); | ||
}; | ||
|
||
export const deleteVolumeSnapshot = ( | ||
volume: LxdStorageVolume, | ||
snapshot: Pick<LxdVolumeSnapshot, "name">, | ||
): Promise<LxdOperationResponse> => { | ||
return new Promise((resolve, reject) => { | ||
fetch( | ||
`/1.0/storage-pools/${volume.pool}/volumes/${volume.type}/${volume.name}/snapshots/${snapshot.name}?project=${volume.project}`, | ||
{ | ||
method: "DELETE", | ||
}, | ||
) | ||
.then(handleResponse) | ||
.then(resolve) | ||
.catch(reject); | ||
}); | ||
}; | ||
|
||
export const deleteVolumeSnapshotBulk = ( | ||
volume: LxdStorageVolume, | ||
snapshotNames: string[], | ||
eventQueue: EventQueue, | ||
): Promise<PromiseSettledResult<void>[]> => { | ||
const results: PromiseSettledResult<void>[] = []; | ||
return new Promise((resolve) => { | ||
void Promise.allSettled( | ||
snapshotNames.map(async (name) => { | ||
return await deleteVolumeSnapshot(volume, { name }).then( | ||
(operation) => { | ||
eventQueue.set( | ||
operation.metadata.id, | ||
() => pushSuccess(results), | ||
(msg) => pushFailure(results, msg), | ||
() => continueOrFinish(results, snapshotNames.length, resolve), | ||
); | ||
}, | ||
); | ||
}), | ||
); | ||
}); | ||
}; | ||
|
||
// NOTE: this api endpoint results in a synchronous operation | ||
export const restoreVolumeSnapshot = ( | ||
volume: LxdStorageVolume, | ||
snapshot: LxdVolumeSnapshot, | ||
): Promise<LxdSyncResponse> => { | ||
return new Promise((resolve, reject) => { | ||
fetch( | ||
`/1.0/storage-pools/${volume.pool}/volumes/${volume.type}/${volume.name}?project=${volume.project}`, | ||
{ | ||
method: "PUT", | ||
body: JSON.stringify({ | ||
restore: snapshot.name, | ||
}), | ||
}, | ||
) | ||
.then(handleResponse) | ||
.then(resolve) | ||
.catch(reject); | ||
}); | ||
}; | ||
|
||
export const renameVolumeSnapshot = (args: { | ||
volume: LxdStorageVolume; | ||
snapshot: LxdVolumeSnapshot; | ||
newName: string; | ||
}): Promise<LxdOperationResponse> => { | ||
const { volume, snapshot, newName } = args; | ||
return new Promise((resolve, reject) => { | ||
fetch( | ||
`/1.0/storage-pools/${volume.pool}/volumes/${volume.type}/${volume.name}/snapshots/${snapshot.name}?project=${volume.project}`, | ||
{ | ||
method: "POST", | ||
body: JSON.stringify({ | ||
name: newName, | ||
}), | ||
}, | ||
) | ||
.then(handleResponse) | ||
.then(resolve) | ||
.catch(reject); | ||
}); | ||
}; | ||
|
||
// NOTE: this api endpoint results in a synchronous operation | ||
export const updateVolumeSnapshot = (args: { | ||
volume: LxdStorageVolume; | ||
snapshot: LxdVolumeSnapshot; | ||
expiresAt: string | null; | ||
description: string; | ||
}): Promise<LxdSyncResponse> => { | ||
const { volume, snapshot, expiresAt, description } = args; | ||
return new Promise((resolve, reject) => { | ||
fetch( | ||
`/1.0/storage-pools/${volume.pool}/volumes/${volume.type}/${volume.name}/snapshots/${snapshot.name}?project=${volume.project}`, | ||
{ | ||
method: "PUT", | ||
body: JSON.stringify({ | ||
expires_at: expiresAt, | ||
description: description, | ||
}), | ||
}, | ||
) | ||
.then(handleResponse) | ||
.then(resolve) | ||
.catch(reject); | ||
}); | ||
}; | ||
|
||
export const fetchStorageVolumeSnapshots = (args: { | ||
pool: string; | ||
type: string; | ||
volumeName: string; | ||
project: string; | ||
}) => { | ||
const { pool, type, volumeName, project } = args; | ||
return new Promise<LxdVolumeSnapshot[]>((resolve, reject) => { | ||
fetch( | ||
`/1.0/storage-pools/${pool}/volumes/${type}/${volumeName}/snapshots?project=${project}&recursion=2`, | ||
) | ||
.then(handleResponse) | ||
.then((data: LxdApiResponse<LxdVolumeSnapshot[]>) => | ||
resolve(data.metadata), | ||
) | ||
.catch(reject); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.