Skip to content

Commit 9ffedb4

Browse files
committed
Support backup cancellation
- Add `backup.cancel` - make non-BC changes to backup types - improve error throwing in backup methods - improve docs of backup types - use mocks to test `waitForCompletion` flow with cancelled backups
1 parent e272b37 commit 9ffedb4

File tree

13 files changed

+485
-109
lines changed

13 files changed

+485
-109
lines changed

src/backup/backupCreateStatusGetter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Connection from '../connection/index.js';
2+
import { WeaviateInvalidInputError } from '../errors.js';
23
import { BackupCreateStatusResponse } from '../openapi/types.js';
34
import { CommandBase } from '../validation/commandBase.js';
45
import { Backend } from './index.js';
@@ -29,7 +30,7 @@ export default class BackupCreateStatusGetter extends CommandBase {
2930
do = (): Promise<BackupCreateStatusResponse> => {
3031
this.validate();
3132
if (this.errors.length > 0) {
32-
return Promise.reject(new Error('invalid usage: ' + this.errors.join(', ')));
33+
return Promise.reject(new WeaviateInvalidInputError('invalid usage: ' + this.errors.join(', ')));
3334
}
3435
return this.client.get(this._path()) as Promise<BackupCreateStatusResponse>;
3536
};

src/backup/backupCreator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Connection from '../connection/index.js';
2+
import { WeaviateInvalidInputError } from '../errors.js';
23
import {
34
BackupConfig,
45
BackupCreateRequest,
@@ -81,7 +82,7 @@ export default class BackupCreator extends CommandBase {
8182
do = (): Promise<BackupCreateResponse> => {
8283
this.validate();
8384
if (this.errors.length > 0) {
84-
return Promise.reject(new Error('invalid usage: ' + this.errors.join(', ')));
85+
return Promise.reject(new WeaviateInvalidInputError('invalid usage: ' + this.errors.join(', ')));
8586
}
8687

8788
const payload = {

src/backup/backupGetter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Connection from '../connection/index.js';
2+
import { WeaviateInvalidInputError } from '../errors.js';
23
import { BackupCreateResponse } from '../openapi/types.js';
34
import { CommandBase } from '../validation/commandBase.js';
45
import { Backend } from './index.js';
@@ -23,7 +24,7 @@ export default class BackupGetter extends CommandBase {
2324
do = (): Promise<BackupCreateResponse[]> => {
2425
this.validate();
2526
if (this.errors.length > 0) {
26-
return Promise.reject(new Error('invalid usage: ' + this.errors.join(', ')));
27+
return Promise.reject(new WeaviateInvalidInputError('invalid usage: ' + this.errors.join(', ')));
2728
}
2829

2930
return this.client.get(this._path());

src/backup/backupRestoreStatusGetter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Connection from '../connection/index.js';
2+
import { WeaviateInvalidInputError } from '../errors.js';
23
import { BackupRestoreStatusResponse } from '../openapi/types.js';
34
import { CommandBase } from '../validation/commandBase.js';
45
import { Backend } from './index.js';
@@ -29,7 +30,7 @@ export default class BackupRestoreStatusGetter extends CommandBase {
2930
do = (): Promise<BackupRestoreStatusResponse> => {
3031
this.validate();
3132
if (this.errors.length > 0) {
32-
return Promise.reject(new Error('invalid usage: ' + this.errors.join(', ')));
33+
return Promise.reject(new WeaviateInvalidInputError('invalid usage: ' + this.errors.join(', ')));
3334
}
3435

3536
return this.client.get(this._path());

src/backup/backupRestorer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Connection from '../connection/index.js';
2+
import { WeaviateInvalidInputError } from '../errors.js';
23
import {
34
BackupRestoreRequest,
45
BackupRestoreResponse,
@@ -81,7 +82,7 @@ export default class BackupRestorer extends CommandBase {
8182
do = (): Promise<BackupRestoreResponse> => {
8283
this.validate();
8384
if (this.errors.length > 0) {
84-
return Promise.reject(new Error('invalid usage: ' + this.errors.join(', ')));
85+
return Promise.reject(new WeaviateInvalidInputError('invalid usage: ' + this.errors.join(', ')));
8586
}
8687

8788
const payload = {

src/collections/backup/client.ts

Lines changed: 135 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,111 @@
11
import {
22
Backend,
3-
BackupCompressionLevel,
43
BackupCreateStatusGetter,
54
BackupCreator,
65
BackupRestoreStatusGetter,
76
BackupRestorer,
87
} from '../../backup/index.js';
8+
import { validateBackend, validateBackupId } from '../../backup/validation.js';
99
import Connection from '../../connection/index.js';
10-
import { WeaviateBackupFailed } from '../../errors.js';
10+
import {
11+
WeaviateBackupCanceled,
12+
WeaviateBackupCancellationError,
13+
WeaviateBackupFailed,
14+
WeaviateInvalidInputError,
15+
WeaviateUnexpectedResponseError,
16+
WeaviateUnexpectedStatusCodeError,
17+
} from '../../errors.js';
1118
import {
1219
BackupCreateResponse,
1320
BackupCreateStatusResponse,
1421
BackupRestoreResponse,
15-
BackupRestoreStatusResponse,
1622
} from '../../openapi/types.js';
17-
18-
/** Configuration options available when creating a backup */
19-
export type BackupConfigCreate = {
20-
/** The size of the chunks to use for the backup. */
21-
chunkSize?: number;
22-
/** The standard of compression to use for the backup. */
23-
compressionLevel?: BackupCompressionLevel;
24-
/** The percentage of CPU to use for the backup creation job. */
25-
cpuPercentage?: number;
26-
};
27-
28-
/** Configuration options available when restoring a backup */
29-
export type BackupConfigRestore = {
30-
/** The percentage of CPU to use for the backuop restoration job. */
31-
cpuPercentage?: number;
32-
};
33-
34-
/** The arguments required to create and restore backups. */
35-
export type BackupArgs<C extends BackupConfigCreate | BackupConfigRestore> = {
36-
/** The ID of the backup. */
37-
backupId: string;
38-
/** The backend to use for the backup. */
39-
backend: Backend;
40-
/** The collections to include in the backup. */
41-
includeCollections?: string[];
42-
/** The collections to exclude from the backup. */
43-
excludeCollections?: string[];
44-
/** Whether to wait for the backup to complete. */
45-
waitForCompletion?: boolean;
46-
/** The configuration options for the backup. */
47-
config?: C;
48-
};
49-
50-
/** The arguments required to get the status of a backup. */
51-
export type BackupStatusArgs = {
52-
/** The ID of the backup. */
53-
backupId: string;
54-
/** The backend to use for the backup. */
55-
backend: Backend;
56-
};
23+
import {
24+
BackupArgs,
25+
BackupCancelArgs,
26+
BackupConfigCreate,
27+
BackupConfigRestore,
28+
BackupReturn,
29+
BackupStatusArgs,
30+
BackupStatusReturn,
31+
} from './types.js';
5732

5833
export const backup = (connection: Connection) => {
59-
const getCreateStatus = (args: BackupStatusArgs): Promise<BackupCreateStatusResponse> => {
34+
const parseStatus = (res: BackupCreateStatusResponse | BackupRestoreResponse): BackupStatusReturn => {
35+
if (res.id === undefined) {
36+
throw new WeaviateUnexpectedResponseError('Backup ID is undefined in response');
37+
}
38+
if (res.path === undefined) {
39+
throw new WeaviateUnexpectedResponseError('Backup path is undefined in response');
40+
}
41+
if (res.status === undefined) {
42+
throw new WeaviateUnexpectedResponseError('Backup status is undefined in response');
43+
}
44+
return {
45+
id: res.id,
46+
error: res.error,
47+
path: res.path,
48+
status: res.status,
49+
};
50+
};
51+
const parseResponse = (res: BackupCreateResponse | BackupRestoreResponse): BackupReturn => {
52+
if (res.id === undefined) {
53+
throw new WeaviateUnexpectedResponseError('Backup ID is undefined in response');
54+
}
55+
if (res.backend === undefined) {
56+
throw new WeaviateUnexpectedResponseError('Backup backend is undefined in response');
57+
}
58+
if (res.path === undefined) {
59+
throw new WeaviateUnexpectedResponseError('Backup path is undefined in response');
60+
}
61+
if (res.status === undefined) {
62+
throw new WeaviateUnexpectedResponseError('Backup status is undefined in response');
63+
}
64+
return {
65+
id: res.id,
66+
backend: res.backend as Backend,
67+
collections: res.classes ? res.classes : [],
68+
error: res.error,
69+
path: res.path,
70+
status: res.status,
71+
};
72+
};
73+
const getCreateStatus = (args: BackupStatusArgs): Promise<BackupStatusReturn> => {
6074
return new BackupCreateStatusGetter(connection)
6175
.withBackupId(args.backupId)
6276
.withBackend(args.backend)
63-
.do();
77+
.do()
78+
.then(parseStatus);
6479
};
65-
const getRestoreStatus = (args: BackupStatusArgs): Promise<BackupRestoreStatusResponse> => {
80+
const getRestoreStatus = (args: BackupStatusArgs): Promise<BackupStatusReturn> => {
6681
return new BackupRestoreStatusGetter(connection)
6782
.withBackupId(args.backupId)
6883
.withBackend(args.backend)
69-
.do();
84+
.do()
85+
.then(parseStatus);
7086
};
7187
return {
72-
create: async (args: BackupArgs<BackupConfigCreate>): Promise<BackupCreateResponse> => {
88+
cancel: async (args: BackupCancelArgs): Promise<boolean> => {
89+
let errors: string[] = [];
90+
errors = errors.concat(validateBackupId(args.backupId)).concat(validateBackend(args.backend));
91+
if (errors.length > 0) {
92+
throw new WeaviateInvalidInputError(errors.join(', '));
93+
}
94+
95+
try {
96+
await connection.delete(`/backups/${args.backend}/${args.backupId}`, undefined, false);
97+
} catch (err) {
98+
if (err instanceof WeaviateUnexpectedStatusCodeError) {
99+
if (err.code === 404) {
100+
return false;
101+
}
102+
throw new WeaviateBackupCancellationError(err.message);
103+
}
104+
}
105+
106+
return true;
107+
},
108+
create: async (args: BackupArgs<BackupConfigCreate>): Promise<BackupReturn> => {
73109
let builder = new BackupCreator(connection, new BackupCreateStatusGetter(connection))
74110
.withBackupId(args.backupId)
75111
.withBackend(args.backend);
@@ -90,31 +126,34 @@ export const backup = (connection: Connection) => {
90126
try {
91127
res = await builder.do();
92128
} catch (err) {
93-
throw new Error(`Backup creation failed: ${err}`);
129+
throw new WeaviateBackupFailed(`Backup creation failed: ${err}`, 'creation');
94130
}
95131
if (res.status === 'FAILED') {
96-
throw new Error(`Backup creation failed: ${res.error}`);
132+
throw new WeaviateBackupFailed(`Backup creation failed: ${res.error}`, 'creation');
97133
}
98-
let status: BackupCreateStatusResponse | undefined;
134+
let status: BackupStatusReturn | undefined;
99135
if (args.waitForCompletion) {
100136
let wait = true;
101137
while (wait) {
102-
const res = await getCreateStatus(args); // eslint-disable-line no-await-in-loop
103-
if (res.status === 'SUCCESS') {
138+
const ret = await getCreateStatus(args); // eslint-disable-line no-await-in-loop
139+
if (ret.status === 'SUCCESS') {
104140
wait = false;
105-
status = res;
141+
status = ret;
142+
}
143+
if (ret.status === 'FAILED') {
144+
throw new WeaviateBackupFailed(ret.error ? ret.error : '<unknown>', 'creation');
106145
}
107-
if (res.status === 'FAILED') {
108-
throw new WeaviateBackupFailed(res.error ? res.error : '<unknown>', 'creation');
146+
if (ret.status === 'CANCELED') {
147+
throw new WeaviateBackupCanceled('creation');
109148
}
110149
await new Promise((resolve) => setTimeout(resolve, 1000)); // eslint-disable-line no-await-in-loop
111150
}
112151
}
113-
return status ? { ...status, classes: res.classes } : res;
152+
return status ? { ...parseResponse(res), ...status } : parseResponse(res);
114153
},
115154
getCreateStatus: getCreateStatus,
116155
getRestoreStatus: getRestoreStatus,
117-
restore: async (args: BackupArgs<BackupConfigRestore>): Promise<BackupRestoreResponse> => {
156+
restore: async (args: BackupArgs<BackupConfigRestore>): Promise<BackupReturn> => {
118157
let builder = new BackupRestorer(connection, new BackupRestoreStatusGetter(connection))
119158
.withBackupId(args.backupId)
120159
.withBackend(args.backend);
@@ -133,63 +172,83 @@ export const backup = (connection: Connection) => {
133172
try {
134173
res = await builder.do();
135174
} catch (err) {
136-
throw new Error(`Backup restoration failed: ${err}`);
175+
throw new WeaviateBackupFailed(`Backup restoration failed: ${err}`, 'restoration');
137176
}
138177
if (res.status === 'FAILED') {
139-
throw new Error(`Backup restoration failed: ${res.error}`);
178+
throw new WeaviateBackupFailed(`Backup restoration failed: ${res.error}`, 'restoration');
140179
}
141-
let status: BackupRestoreStatusResponse | undefined;
180+
let status: BackupStatusReturn | undefined;
142181
if (args.waitForCompletion) {
143182
let wait = true;
144183
while (wait) {
145-
const res = await getRestoreStatus(args); // eslint-disable-line no-await-in-loop
146-
if (res.status === 'SUCCESS') {
184+
const ret = await getRestoreStatus(args); // eslint-disable-line no-await-in-loop
185+
if (ret.status === 'SUCCESS') {
147186
wait = false;
148-
status = res;
187+
status = ret;
188+
}
189+
if (ret.status === 'FAILED') {
190+
throw new WeaviateBackupFailed(ret.error ? ret.error : '<unknown>', 'restoration');
149191
}
150-
if (res.status === 'FAILED') {
151-
throw new WeaviateBackupFailed(res.error ? res.error : '<unknown>', 'restoration');
192+
if (ret.status === 'CANCELED') {
193+
throw new WeaviateBackupCanceled('restoration');
152194
}
153195
await new Promise((resolve) => setTimeout(resolve, 1000)); // eslint-disable-line no-await-in-loop
154196
}
155197
}
156198
return status
157199
? {
200+
...parseResponse(res),
158201
...status,
159-
classes: res.classes,
160202
}
161-
: res;
203+
: parseResponse(res);
162204
},
163205
};
164206
};
165207

166208
export interface Backup {
209+
/**
210+
* Cancel a backup.
211+
*
212+
* @param {BackupCancelArgs} args The arguments for the request.
213+
* @returns {Promise<boolean>} Whether the backup was canceled.
214+
* @throws {WeaviateInvalidInputError} If the input is invalid.
215+
* @throws {WeaviateBackupCancellationError} If the backup cancellation fails.
216+
*/
217+
cancel(args: BackupCancelArgs): Promise<boolean>;
167218
/**
168219
* Create a backup of the database.
169220
*
170221
* @param {BackupArgs} args The arguments for the request.
171-
* @returns {Promise<BackupCreateResponse>} The response from Weaviate.
222+
* @returns {Promise<BackupReturn>} The response from Weaviate.
223+
* @throws {WeaviateInvalidInputError} If the input is invalid.
224+
* @throws {WeaviateBackupFailed} If the backup creation fails.
225+
* @throws {WeaviateBackupCanceled} If the backup creation is canceled.
172226
*/
173-
create(args: BackupArgs<BackupConfigCreate>): Promise<BackupCreateResponse>;
227+
create(args: BackupArgs<BackupConfigCreate>): Promise<BackupReturn>;
174228
/**
175229
* Get the status of a backup creation.
176230
*
177231
* @param {BackupStatusArgs} args The arguments for the request.
178-
* @returns {Promise<BackupCreateStatusResponse>} The status of the backup creation.
232+
* @returns {Promise<BackupStatusReturn>} The status of the backup creation.
233+
* @throws {WeaviateInvalidInputError} If the input is invalid.
179234
*/
180-
getCreateStatus(args: BackupStatusArgs): Promise<BackupCreateStatusResponse>;
235+
getCreateStatus(args: BackupStatusArgs): Promise<BackupStatusReturn>;
181236
/**
182237
* Get the status of a backup restore.
183238
*
184239
* @param {BackupStatusArgs} args The arguments for the request.
185-
* @returns {Promise<BackupRestoreStatusResponse>} The status of the backup restore.
240+
* @returns {Promise<BackupStatusReturn>} The status of the backup restore.
241+
* @throws {WeaviateInvalidInputError} If the input is invalid.
186242
*/
187-
getRestoreStatus(args: BackupStatusArgs): Promise<BackupRestoreStatusResponse>;
243+
getRestoreStatus(args: BackupStatusArgs): Promise<BackupStatusReturn>;
188244
/**
189245
* Restore a backup of the database.
190246
*
191247
* @param {BackupArgs} args The arguments for the request.
192-
* @returns {Promise<BackupRestoreResponse>} The response from Weaviate.
248+
* @returns {Promise<BackupReturn>} The response from Weaviate.
249+
* @throws {WeaviateInvalidInputError} If the input is invalid.
250+
* @throws {WeaviateBackupFailed} If the backup restoration fails.
251+
* @throws {WeaviateBackupCanceled} If the backup restoration is canceled.
193252
*/
194-
restore(args: BackupArgs<BackupConfigRestore>): Promise<BackupRestoreResponse>;
253+
restore(args: BackupArgs<BackupConfigRestore>): Promise<BackupReturn>;
195254
}

0 commit comments

Comments
 (0)