Skip to content

Commit 07fcf88

Browse files
dbbackuprestore: add KMS encryption key and status message support to database restore (#303)
1 parent 7d3c34b commit 07fcf88

File tree

9 files changed

+140
-2
lines changed

9 files changed

+140
-2
lines changed

dbbackuprestore/dbbackuprestore-ali/src/main/java/com/salesforce/multicloudj/dbbackuprestore/ali/AliDBBackupRestore.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ private Restore convertToRestore(DescribeRestoreJobs2ResponseBody.DescribeRestor
269269
.status(status)
270270
.startTime(startTime)
271271
.endTime(endTime)
272+
.statusMessage(restoreJob.getErrorMessage())
272273
.build();
273274
}
274275

dbbackuprestore/dbbackuprestore-ali/src/test/java/com/salesforce/multicloudj/dbbackuprestore/ali/AliDBBackupRestoreTest.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,13 @@ void testRestoreBackupFailure() throws Exception {
208208
});
209209
}
210210

211-
private Restore setupGetRestoreJobTest(String restoreJobId, String status, boolean includeCompletionTime) throws Exception {
211+
private Restore setupGetRestoreJobTest(String restoreJobId, String status, boolean includeCompletionTime)
212+
throws Exception {
213+
return setupGetRestoreJobTest(restoreJobId, status, includeCompletionTime, null);
214+
}
215+
216+
private Restore setupGetRestoreJobTest(
217+
String restoreJobId, String status, boolean includeCompletionTime, String errorMessage) throws Exception {
212218
Instant creationTime = Instant.now().minusSeconds(300);
213219
Instant completionTime = Instant.now();
214220

@@ -223,6 +229,9 @@ private Restore setupGetRestoreJobTest(String restoreJobId, String status, boole
223229
if (includeCompletionTime) {
224230
restoreJob.setCompleteTime(completionTime.getEpochSecond());
225231
}
232+
if (errorMessage != null) {
233+
restoreJob.setErrorMessage(errorMessage);
234+
}
226235

227236
DescribeRestoreJobs2ResponseBody.DescribeRestoreJobs2ResponseBodyRestoreJobs restoreJobs =
228237
new DescribeRestoreJobs2ResponseBody.DescribeRestoreJobs2ResponseBodyRestoreJobs();
@@ -275,6 +284,17 @@ void testGetRestore_Job_Failed() throws Exception {
275284
assertNotNull(restore.getEndTime());
276285
}
277286

287+
@Test
288+
void testGetRestore_Job_Failed_WithStatusMessage() throws Exception {
289+
String restoreJobId = "restore-job-789";
290+
String failureMessage = "Snapshot not found: snapshot-789";
291+
Restore restore = setupGetRestoreJobTest(restoreJobId, "FAILED", true, failureMessage);
292+
assertEquals(RestoreStatus.FAILED, restore.getStatus());
293+
assertEquals(failureMessage, restore.getStatusMessage());
294+
assertNotNull(restore.getStartTime());
295+
assertNotNull(restore.getEndTime());
296+
}
297+
278298
@Test
279299
void testGetRestore_Job_NotFound() throws Exception {
280300
String restoreJobId = "non-existent-restore";

dbbackuprestore/dbbackuprestore-aws/src/main/java/com/salesforce/multicloudj/dbbackuprestore/aws/AwsDBBackupRestore.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ public String restoreBackup(RestoreRequest request) {
118118
Map<String, String> metadata = new HashMap<>();
119119
metadata.put("targetTableName", targetTableName);
120120

121+
if (StringUtils.isNotBlank(request.getKmsEncryptionKeyId())) {
122+
metadata.put("encryptionType", "KMS");
123+
metadata.put("kmsMasterKeyArn", request.getKmsEncryptionKeyId());
124+
}
125+
121126
StartRestoreJobRequest restoreJobRequest = StartRestoreJobRequest.builder().recoveryPointArn(request.getBackupId()).metadata(metadata).idempotencyToken(UUID.uniqueString()).iamRoleArn(iamRoleArn).resourceType("DynamoDB").build();
122127

123128
StartRestoreJobResponse response = backupClient.startRestoreJob(restoreJobRequest);
@@ -199,6 +204,7 @@ private Restore convertToRestore(DescribeRestoreJobResponse response) {
199204
.status(convertRestoreJobStatus(response.status()))
200205
.startTime(response.creationDate())
201206
.endTime(response.completionDate())
207+
.statusMessage(response.statusMessage())
202208
.build();
203209
}
204210

dbbackuprestore/dbbackuprestore-aws/src/test/java/com/salesforce/multicloudj/dbbackuprestore/aws/AwsDBBackupRestoreTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,37 @@ void testRestoreBackup() {
168168
assertEquals("DynamoDB", capturedRequest.resourceType());
169169
assertTrue(capturedRequest.metadata().containsKey("targetTableName"));
170170
assertEquals("restored-table", capturedRequest.metadata().get("targetTableName"));
171+
Assertions.assertFalse(capturedRequest.metadata().containsKey("encryptionType"));
172+
Assertions.assertFalse(capturedRequest.metadata().containsKey("kmsMasterKeyArn"));
173+
}
174+
175+
@Test
176+
void testRestoreBackupWithKmsEncryption() {
177+
String kmsKeyArn = "arn:aws:kms:us-west-2:123456789012:key/abc-def-123";
178+
RestoreRequest request = RestoreRequest.builder()
179+
.backupId("arn:aws:backup:us-west-2:123456789012:recovery-point:789")
180+
.targetResource("restored-table")
181+
.roleId(IAM_ROLE_ARN)
182+
.kmsEncryptionKeyId(kmsKeyArn)
183+
.build();
184+
185+
StartRestoreJobResponse response = StartRestoreJobResponse.builder()
186+
.restoreJobId("restore-job-456")
187+
.build();
188+
189+
when(mockBackupClient.startRestoreJob(any(StartRestoreJobRequest.class)))
190+
.thenReturn(response);
191+
192+
String restoreJobId = dbBackupRestore.restoreBackup(request);
193+
assertEquals("restore-job-456", restoreJobId);
194+
195+
ArgumentCaptor<StartRestoreJobRequest> captor = ArgumentCaptor.forClass(StartRestoreJobRequest.class);
196+
verify(mockBackupClient, times(1)).startRestoreJob(captor.capture());
197+
198+
StartRestoreJobRequest capturedRequest = captor.getValue();
199+
assertEquals("restored-table", capturedRequest.metadata().get("targetTableName"));
200+
assertEquals("KMS", capturedRequest.metadata().get("encryptionType"));
201+
assertEquals(kmsKeyArn, capturedRequest.metadata().get("kmsMasterKeyArn"));
171202
}
172203

173204
@Test
@@ -184,6 +215,10 @@ void testRestoreBackupMissingRole() {
184215
}
185216

186217
private Restore setupGetRestoreJobTest(String restoreJobId, RestoreJobStatus status, boolean includeCompletionDate) {
218+
return setupGetRestoreJobTest(restoreJobId, status, includeCompletionDate, null);
219+
}
220+
221+
private Restore setupGetRestoreJobTest(String restoreJobId, RestoreJobStatus status, boolean includeCompletionDate, String statusMessage) {
187222
Instant creationTime = Instant.now().minusSeconds(300);
188223
Instant completionTime = Instant.now();
189224

@@ -197,6 +232,9 @@ private Restore setupGetRestoreJobTest(String restoreJobId, RestoreJobStatus sta
197232
if (includeCompletionDate) {
198233
responseBuilder.completionDate(completionTime);
199234
}
235+
if (statusMessage != null) {
236+
responseBuilder.statusMessage(statusMessage);
237+
}
200238

201239
when(mockBackupClient.describeRestoreJob(any(DescribeRestoreJobRequest.class)))
202240
.thenReturn(responseBuilder.build());
@@ -242,6 +280,18 @@ void testGetRestore_Job_Failed() {
242280
assertNotNull(restore.getEndTime());
243281
}
244282

283+
@Test
284+
void testGetRestore_Job_Failed_WithStatusMessage() {
285+
String restoreJobId = "restore-job-789";
286+
String failureMessage = "Table already exists: temp-restore-table";
287+
Restore restore = setupGetRestoreJobTest(restoreJobId, RestoreJobStatus.FAILED, true, failureMessage);
288+
289+
assertEquals(RestoreStatus.FAILED, restore.getStatus());
290+
assertEquals(failureMessage, restore.getStatusMessage());
291+
assertNotNull(restore.getStartTime());
292+
assertNotNull(restore.getEndTime());
293+
}
294+
245295
@Test
246296
void testGetRestore_Job_Pending() {
247297
String restoreJobId = "restore-job-pending";

dbbackuprestore/dbbackuprestore-client/src/main/java/com/salesforce/multicloudj/dbbackuprestore/driver/Restore.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,10 @@ public class Restore {
5151
* Will be null if the restore is still in progress.
5252
*/
5353
private Instant endTime;
54+
55+
/**
56+
* Status or failure message from the restore job.
57+
* For failed jobs, this typically contains the provider-specific restore error reason.
58+
*/
59+
private String statusMessage;
5460
}

dbbackuprestore/dbbackuprestore-client/src/main/java/com/salesforce/multicloudj/dbbackuprestore/driver/RestoreRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,9 @@ public class RestoreRequest {
4242
* is required to locate and restore from a backup.
4343
*/
4444
private String vaultId;
45+
46+
/**
47+
* KMS encryption key identifier for encrypting the restored resource.
48+
*/
49+
private String kmsEncryptionKeyId;
4550
}

dbbackuprestore/dbbackuprestore-client/src/test/java/com/salesforce/multicloudj/dbbackuprestore/driver/RestoreRequestTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ void testRestoreRequestBuilder() {
1616
.targetResource("restored-table")
1717
.roleId("arn:aws:iam::123456789012:role/RestoreRole")
1818
.vaultId("vault-abc")
19+
.kmsEncryptionKeyId("arn:aws:kms:us-west-2:123456789012:key/abc-def")
1920
.build();
2021

2122
assertEquals("backup-123", request.getBackupId());
2223
assertEquals("restored-table", request.getTargetResource());
2324
assertEquals("arn:aws:iam::123456789012:role/RestoreRole", request.getRoleId());
2425
assertEquals("vault-abc", request.getVaultId());
26+
assertEquals("arn:aws:kms:us-west-2:123456789012:key/abc-def", request.getKmsEncryptionKeyId());
2527
}
2628

2729
@Test
@@ -32,11 +34,13 @@ void testRestoreRequestSetters() {
3234
request.setTargetResource("new-table");
3335
request.setRoleId("role-123");
3436
request.setVaultId("vault-xyz");
37+
request.setKmsEncryptionKeyId("arn:aws:kms:us-west-2:123456789012:key/xyz");
3538

3639
assertEquals("backup-789", request.getBackupId());
3740
assertEquals("new-table", request.getTargetResource());
3841
assertEquals("role-123", request.getRoleId());
3942
assertEquals("vault-xyz", request.getVaultId());
43+
assertEquals("arn:aws:kms:us-west-2:123456789012:key/xyz", request.getKmsEncryptionKeyId());
4044
}
4145

4246
@Test
@@ -45,12 +49,14 @@ void testRestoreRequestAllArgsConstructor() {
4549
"backup-all",
4650
"target-table",
4751
"role-arn",
48-
"vault-id"
52+
"vault-id",
53+
"arn:aws:kms:us-west-2:123456789012:key/all-args"
4954
);
5055

5156
assertEquals("backup-all", request.getBackupId());
5257
assertEquals("target-table", request.getTargetResource());
5358
assertEquals("role-arn", request.getRoleId());
5459
assertEquals("vault-id", request.getVaultId());
60+
assertEquals("arn:aws:kms:us-west-2:123456789012:key/all-args", request.getKmsEncryptionKeyId());
5561
}
5662
}

dbbackuprestore/dbbackuprestore-gcp-firestore/src/main/java/com/salesforce/multicloudj/dbbackuprestore/gcp/FSDBBackupRestore.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ public String restoreBackup(RestoreRequest request) {
118118
.setDatabaseId(targetDBID)
119119
.setBackup(request.getBackupId());
120120

121+
if (StringUtils.isNotBlank(request.getKmsEncryptionKeyId())) {
122+
restoreBuilder.setEncryptionConfig(
123+
Database.EncryptionConfig.newBuilder()
124+
.setCustomerManagedEncryption(
125+
Database.EncryptionConfig.CustomerManagedEncryptionOptions.newBuilder()
126+
.setKmsKeyName(request.getKmsEncryptionKeyId())
127+
.build())
128+
.build());
129+
}
130+
121131
// Restore is a long-running operation
122132
OperationFuture<Database, RestoreDatabaseMetadata> operation =
123133
firestoreAdminClient.restoreDatabaseAsync(restoreBuilder.build());
@@ -191,9 +201,11 @@ private Restore convertToRestore(Operation operation) {
191201
Instant endTime = null;
192202
Instant startTime = null;
193203

204+
String statusMessage = null;
194205
if (operation.getDone()) {
195206
if (operation.hasError()) {
196207
status = RestoreStatus.FAILED;
208+
statusMessage = operation.getError().getMessage();
197209
} else {
198210
status = RestoreStatus.COMPLETED;
199211
}
@@ -228,6 +240,7 @@ private Restore convertToRestore(Operation operation) {
228240
.status(status)
229241
.startTime(startTime)
230242
.endTime(endTime)
243+
.statusMessage(statusMessage)
231244
.build();
232245
}
233246

dbbackuprestore/dbbackuprestore-gcp-firestore/src/test/java/com/salesforce/multicloudj/dbbackuprestore/gcp/FSDBBackupRestoreTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,37 @@ void testRestoreBackup() throws Exception {
161161
assertEquals("projects/my-project", capturedRequest.getParent());
162162
}
163163

164+
@Test
165+
void testRestoreBackup_WithCmekEncryption() throws Exception {
166+
String kmsKeyName = "projects/my-project/locations/us-central1/keyRings/my-ring/cryptoKeys/my-key";
167+
RestoreRequest request = RestoreRequest.builder()
168+
.backupId("projects/my-project/locations/us-central1/backups/backup-789")
169+
.targetResource("restored-db")
170+
.kmsEncryptionKeyId(kmsKeyName)
171+
.build();
172+
173+
@SuppressWarnings("unchecked")
174+
OperationFuture<Database, RestoreDatabaseMetadata> mockOperationFuture =
175+
org.mockito.Mockito.mock(OperationFuture.class);
176+
when(mockOperationFuture.getName()).thenReturn("operations/restore-job-456");
177+
178+
when(mockFirestoreClient.restoreDatabaseAsync(any(RestoreDatabaseRequest.class)))
179+
.thenReturn(mockOperationFuture);
180+
181+
String restoreId = dbBackupRestore.restoreBackup(request);
182+
183+
assertEquals("operations/restore-job-456", restoreId);
184+
185+
ArgumentCaptor<RestoreDatabaseRequest> captor = ArgumentCaptor.forClass(RestoreDatabaseRequest.class);
186+
verify(mockFirestoreClient, times(1)).restoreDatabaseAsync(captor.capture());
187+
188+
RestoreDatabaseRequest capturedRequest = captor.getValue();
189+
assertTrue(capturedRequest.hasEncryptionConfig());
190+
assertEquals(
191+
kmsKeyName,
192+
capturedRequest.getEncryptionConfig().getCustomerManagedEncryption().getKmsKeyName());
193+
}
194+
164195
private Restore setupGetRestoreJobTest(String restoreId, boolean isDone, boolean hasError, boolean includeEndTime) {
165196
Instant startTime = Instant.now().minusSeconds(300);
166197
Instant endTime = Instant.now();

0 commit comments

Comments
 (0)