Skip to content

Commit

Permalink
Merge pull request #85 from depot/feat/create-rm-snapshots-clones
Browse files Browse the repository at this point in the history
feat: handle creating and removing snapshots and clones
  • Loading branch information
goller authored May 17, 2024
2 parents eca21d3 + 335a959 commit 4fa3781
Show file tree
Hide file tree
Showing 4 changed files with 318 additions and 38 deletions.
28 changes: 28 additions & 0 deletions proto/depot/cloud/v2/cloud.proto
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ message ReconcileVolumesResponse {
DeleteClientAction delete_client = 6;

TrimVolumeAction trim_volume = 7;

CopyVolumeAction copy_volume = 8;
}
}

Expand All @@ -209,6 +211,7 @@ message ReportVolumeUpdatesRequest {
DeleteClientUpdate delete_client = 6;

TrimVolumeUpdate trim_volume = 7;
CopyVolumeUpdate copy_volume = 8;
}
}

Expand Down Expand Up @@ -289,3 +292,28 @@ message AuthorizeClientUpdate {
string volume_name = 2;
string image_spec = 3;
}

message CopyVolumeAction {
enum Kind {
KIND_UNSPECIFIED = 0;
KIND_SNAPSHOT = 1;
KIND_CLONE = 2;
}

Kind kind = 1;
string volume_name = 2;
// The parent spec is the volume id of the volume that the snapshot or clone is a child of.
string parent_image_spec = 3;
}

message CopyVolumeUpdate {
enum Kind {
KIND_UNSPECIFIED = 0;
KIND_SNAPSHOT = 1;
KIND_CLONE = 2;
}
Kind kind = 1;
string volume_name = 2;
string image_spec = 3;
string parent_image_spec = 4;
}
89 changes: 88 additions & 1 deletion src/handlers/volumes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {PlainMessage} from '@bufbuild/protobuf'
import {
AuthorizeClientAction,
CopyVolumeAction,
CopyVolumeAction_Kind,
CopyVolumeUpdate_Kind,
CreateClientAction,
CreateVolumeAction,
DeleteClientAction,
Expand All @@ -17,14 +20,20 @@ import {
cephConfig,
createAuthEntity,
createBlockDevice,
createClone,
createNamespace,
createSnapshot,
enableCephMetrics,
imageRm,
namespaceRm,
newClientName,
newCloneSpec,
newImageSpec,
newOsdProfile,
newPoolSpec,
newSnapshotSpec,
snapshotFromImageSpec,
snapshotRm,
sparsify,
} from '../utils/ceph'
import {reportError} from '../utils/errors'
Expand Down Expand Up @@ -70,6 +79,8 @@ async function handleAction(
switch (action.case) {
case 'createVolume':
return await createVolume(action.value)
case 'copyVolume':
return await copyVolume(action.value)
case 'resizeVolume':
return await resizeVolume(action.value)
case 'trimVolume':
Expand Down Expand Up @@ -110,6 +121,70 @@ async function createVolume({volumeName, size}: CreateVolumeAction): Promise<Pla
}
}

async function copyVolume({
volumeName,
parentImageSpec,
kind,
}: CopyVolumeAction): Promise<PlainMessage<ReportVolumeUpdatesRequest> | null> {
switch (kind) {
case CopyVolumeAction_Kind.SNAPSHOT:
return await snapshotVolume(volumeName, parentImageSpec)
case CopyVolumeAction_Kind.CLONE:
return await cloneVolume(volumeName, parentImageSpec)
default:
return null
}
}

async function snapshotVolume(
volumeName: string,
parentImage: string,
): Promise<PlainMessage<ReportVolumeUpdatesRequest>> {
const parentImageSpec = newImageSpec(parentImage)
const snapshotSpec = snapshotFromImageSpec(parentImageSpec, volumeName)
await createSnapshot(snapshotSpec)

return {
update: {
case: 'copyVolume',
value: {
kind: CopyVolumeUpdate_Kind.SNAPSHOT,
volumeName,
imageSpec: snapshotSpec,
parentImageSpec,
},
},
}
}

async function cloneVolume(
volumeName: string,
parentImageSpec: string,
): Promise<PlainMessage<ReportVolumeUpdatesRequest> | null> {
// PRECONDITION: parent name is a snapshot name with the `@` symbol.
if (!parentImageSpec.includes('@')) {
console.error(`Invalid snapshot name: ${parentImageSpec}`)
return null
}
const [snapshotParentName] = parentImageSpec.split('@')
const snapshotSpec = newSnapshotSpec(parentImageSpec)

const cloneSpec = newCloneSpec(newPoolSpec(snapshotParentName), volumeName)
await createClone(snapshotSpec, cloneSpec)

return {
update: {
case: 'copyVolume',
value: {
kind: CopyVolumeUpdate_Kind.CLONE,
volumeName,
imageSpec: cloneSpec,
parentImageSpec,
},
},
}
}

async function resizeVolume(_action: ResizeVolumeAction) {
// TODO: resize volume
return null
Expand Down Expand Up @@ -143,8 +218,20 @@ async function trimVolume({volumeName}: TrimVolumeAction): Promise<PlainMessage<
async function deleteVolume({
volumeName,
imageSpec,
}: DeleteVolumeAction): Promise<PlainMessage<ReportVolumeUpdatesRequest>> {
}: DeleteVolumeAction): Promise<PlainMessage<ReportVolumeUpdatesRequest> | null> {
if (imageSpec) {
if (imageSpec.includes('@')) {
const snapshotSpec = newSnapshotSpec(imageSpec)
await snapshotRm(snapshotSpec)
return {
update: {
case: 'deleteVolume',
value: {
volumeName,
},
},
}
}
await imageRm(newImageSpec(imageSpec))
} else {
await imageRm(newImageSpec(volumeName))
Expand Down
180 changes: 180 additions & 0 deletions src/proto/depot/cloud/v2/cloud_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,13 @@ export class ReconcileVolumesResponse extends Message<ReconcileVolumesResponse>
value: TrimVolumeAction
case: 'trimVolume'
}
| {
/**
* @generated from field: depot.cloud.v2.CopyVolumeAction copy_volume = 8;
*/
value: CopyVolumeAction
case: 'copyVolume'
}
| {case: undefined; value?: undefined} = {case: undefined}

constructor(data?: PartialMessage<ReconcileVolumesResponse>) {
Expand All @@ -1425,6 +1432,7 @@ export class ReconcileVolumesResponse extends Message<ReconcileVolumesResponse>
{no: 5, name: 'authorize_client', kind: 'message', T: AuthorizeClientAction, oneof: 'action'},
{no: 6, name: 'delete_client', kind: 'message', T: DeleteClientAction, oneof: 'action'},
{no: 7, name: 'trim_volume', kind: 'message', T: TrimVolumeAction, oneof: 'action'},
{no: 8, name: 'copy_volume', kind: 'message', T: CopyVolumeAction, oneof: 'action'},
])

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ReconcileVolumesResponse {
Expand Down Expand Up @@ -1504,6 +1512,13 @@ export class ReportVolumeUpdatesRequest extends Message<ReportVolumeUpdatesReque
value: TrimVolumeUpdate
case: 'trimVolume'
}
| {
/**
* @generated from field: depot.cloud.v2.CopyVolumeUpdate copy_volume = 8;
*/
value: CopyVolumeUpdate
case: 'copyVolume'
}
| {case: undefined; value?: undefined} = {case: undefined}

constructor(data?: PartialMessage<ReportVolumeUpdatesRequest>) {
Expand All @@ -1521,6 +1536,7 @@ export class ReportVolumeUpdatesRequest extends Message<ReportVolumeUpdatesReque
{no: 5, name: 'authorize_client', kind: 'message', T: AuthorizeClientUpdate, oneof: 'update'},
{no: 6, name: 'delete_client', kind: 'message', T: DeleteClientUpdate, oneof: 'update'},
{no: 7, name: 'trim_volume', kind: 'message', T: TrimVolumeUpdate, oneof: 'update'},
{no: 8, name: 'copy_volume', kind: 'message', T: CopyVolumeUpdate, oneof: 'update'},
])

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ReportVolumeUpdatesRequest {
Expand Down Expand Up @@ -2218,3 +2234,167 @@ export class AuthorizeClientUpdate extends Message<AuthorizeClientUpdate> {
return proto3.util.equals(AuthorizeClientUpdate, a, b)
}
}

/**
* @generated from message depot.cloud.v2.CopyVolumeAction
*/
export class CopyVolumeAction extends Message<CopyVolumeAction> {
/**
* @generated from field: depot.cloud.v2.CopyVolumeAction.Kind kind = 1;
*/
kind = CopyVolumeAction_Kind.UNSPECIFIED

/**
* @generated from field: string volume_name = 2;
*/
volumeName = ''

/**
* The parent spec is the volume id of the volume that the snapshot or clone is a child of.
*
* @generated from field: string parent_image_spec = 3;
*/
parentImageSpec = ''

constructor(data?: PartialMessage<CopyVolumeAction>) {
super()
proto3.util.initPartial(data, this)
}

static readonly runtime: typeof proto3 = proto3
static readonly typeName = 'depot.cloud.v2.CopyVolumeAction'
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{no: 1, name: 'kind', kind: 'enum', T: proto3.getEnumType(CopyVolumeAction_Kind)},
{no: 2, name: 'volume_name', kind: 'scalar', T: 9 /* ScalarType.STRING */},
{no: 3, name: 'parent_image_spec', kind: 'scalar', T: 9 /* ScalarType.STRING */},
])

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CopyVolumeAction {
return new CopyVolumeAction().fromBinary(bytes, options)
}

static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CopyVolumeAction {
return new CopyVolumeAction().fromJson(jsonValue, options)
}

static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CopyVolumeAction {
return new CopyVolumeAction().fromJsonString(jsonString, options)
}

static equals(
a: CopyVolumeAction | PlainMessage<CopyVolumeAction> | undefined,
b: CopyVolumeAction | PlainMessage<CopyVolumeAction> | undefined,
): boolean {
return proto3.util.equals(CopyVolumeAction, a, b)
}
}

/**
* @generated from enum depot.cloud.v2.CopyVolumeAction.Kind
*/
export enum CopyVolumeAction_Kind {
/**
* @generated from enum value: KIND_UNSPECIFIED = 0;
*/
UNSPECIFIED = 0,

/**
* @generated from enum value: KIND_SNAPSHOT = 1;
*/
SNAPSHOT = 1,

/**
* @generated from enum value: KIND_CLONE = 2;
*/
CLONE = 2,
}
// Retrieve enum metadata with: proto3.getEnumType(CopyVolumeAction_Kind)
proto3.util.setEnumType(CopyVolumeAction_Kind, 'depot.cloud.v2.CopyVolumeAction.Kind', [
{no: 0, name: 'KIND_UNSPECIFIED'},
{no: 1, name: 'KIND_SNAPSHOT'},
{no: 2, name: 'KIND_CLONE'},
])

/**
* @generated from message depot.cloud.v2.CopyVolumeUpdate
*/
export class CopyVolumeUpdate extends Message<CopyVolumeUpdate> {
/**
* @generated from field: depot.cloud.v2.CopyVolumeUpdate.Kind kind = 1;
*/
kind = CopyVolumeUpdate_Kind.UNSPECIFIED

/**
* @generated from field: string volume_name = 2;
*/
volumeName = ''

/**
* @generated from field: string image_spec = 3;
*/
imageSpec = ''

/**
* @generated from field: string parent_image_spec = 4;
*/
parentImageSpec = ''

constructor(data?: PartialMessage<CopyVolumeUpdate>) {
super()
proto3.util.initPartial(data, this)
}

static readonly runtime: typeof proto3 = proto3
static readonly typeName = 'depot.cloud.v2.CopyVolumeUpdate'
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{no: 1, name: 'kind', kind: 'enum', T: proto3.getEnumType(CopyVolumeUpdate_Kind)},
{no: 2, name: 'volume_name', kind: 'scalar', T: 9 /* ScalarType.STRING */},
{no: 3, name: 'image_spec', kind: 'scalar', T: 9 /* ScalarType.STRING */},
{no: 4, name: 'parent_image_spec', kind: 'scalar', T: 9 /* ScalarType.STRING */},
])

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CopyVolumeUpdate {
return new CopyVolumeUpdate().fromBinary(bytes, options)
}

static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CopyVolumeUpdate {
return new CopyVolumeUpdate().fromJson(jsonValue, options)
}

static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CopyVolumeUpdate {
return new CopyVolumeUpdate().fromJsonString(jsonString, options)
}

static equals(
a: CopyVolumeUpdate | PlainMessage<CopyVolumeUpdate> | undefined,
b: CopyVolumeUpdate | PlainMessage<CopyVolumeUpdate> | undefined,
): boolean {
return proto3.util.equals(CopyVolumeUpdate, a, b)
}
}

/**
* @generated from enum depot.cloud.v2.CopyVolumeUpdate.Kind
*/
export enum CopyVolumeUpdate_Kind {
/**
* @generated from enum value: KIND_UNSPECIFIED = 0;
*/
UNSPECIFIED = 0,

/**
* @generated from enum value: KIND_SNAPSHOT = 1;
*/
SNAPSHOT = 1,

/**
* @generated from enum value: KIND_CLONE = 2;
*/
CLONE = 2,
}
// Retrieve enum metadata with: proto3.getEnumType(CopyVolumeUpdate_Kind)
proto3.util.setEnumType(CopyVolumeUpdate_Kind, 'depot.cloud.v2.CopyVolumeUpdate.Kind', [
{no: 0, name: 'KIND_UNSPECIFIED'},
{no: 1, name: 'KIND_SNAPSHOT'},
{no: 2, name: 'KIND_CLONE'},
])
Loading

0 comments on commit 4fa3781

Please sign in to comment.