diff --git a/spanner/api/Spanner.Samples.Tests/CopyBackupWithMrCmekAsyncTest.cs b/spanner/api/Spanner.Samples.Tests/CopyBackupWithMrCmekAsyncTest.cs
new file mode 100644
index 00000000000..12cc4247d1a
--- /dev/null
+++ b/spanner/api/Spanner.Samples.Tests/CopyBackupWithMrCmekAsyncTest.cs
@@ -0,0 +1,49 @@
+// Copyright 2024 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.Spanner.Admin.Database.V1;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+[Collection(nameof(SpannerFixture))]
+public class CopyBackupWithMrCmekAsyncTest
+{
+ private readonly SpannerFixture _spannerFixture;
+
+ public CopyBackupWithMrCmekAsyncTest(SpannerFixture spannerFixture)
+ {
+ _spannerFixture = spannerFixture;
+ }
+
+ [SkippableFact]
+ public async Task TestCopyBackupWithMrCmekAsync()
+ {
+ CopyBackupWithMrCmekAsyncSample copyBackupWithMrCmekAsyncSample = new CopyBackupWithMrCmekAsyncSample();
+ string source_Project_id = _spannerFixture.ProjectId;
+ string source_Instance_id = _spannerFixture.InstanceId;
+ string source_backupId = _spannerFixture.MrCmekBackupId;
+ string target_Project_id = _spannerFixture.ProjectId;
+ string target_Instance_id = _spannerFixture.InstanceId;
+ string target_backupId = SpannerFixture.GenerateId("test_", 16);
+ DateTimeOffset expireTime = DateTimeOffset.UtcNow.AddHours(12);
+ CryptoKeyName[] kmsKeyNames = _spannerFixture.KmsKeyNames;
+
+ var backup = await copyBackupWithMrCmekAsyncSample.CopyBackupWithMrCmekAsync(source_Instance_id, source_Project_id, source_backupId,
+ target_Instance_id, target_Project_id, target_backupId, expireTime, kmsKeyNames);
+
+ Assert.All(backup.EncryptionInformation, encryptionInfo => _spannerFixture.KmsKeyNames.Contains(CryptoKeyName.Parse(encryptionInfo.KmsKeyVersionAsCryptoKeyVersionName.CryptoKeyId)));
+ }
+}
diff --git a/spanner/api/Spanner.Samples.Tests/CreateBackupWithMrCmekAsyncTest.cs b/spanner/api/Spanner.Samples.Tests/CreateBackupWithMrCmekAsyncTest.cs
new file mode 100644
index 00000000000..a3472090a89
--- /dev/null
+++ b/spanner/api/Spanner.Samples.Tests/CreateBackupWithMrCmekAsyncTest.cs
@@ -0,0 +1,43 @@
+// Copyright 2024 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.Spanner.Admin.Database.V1;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+///
+/// Tests creating a backup using MR CMEK.
+///
+[Collection(nameof(SpannerFixture))]
+public class CreateBackupWithMrCmekAsyncTest
+{
+ private readonly SpannerFixture _fixture;
+
+ public CreateBackupWithMrCmekAsyncTest(SpannerFixture fixture)
+ {
+ _fixture = fixture;
+ }
+
+ [SkippableFact]
+ public async Task TestCreateBackupWithMrCmekAsync()
+ {
+ Skip.If(!_fixture.RunCmekBackupSampleTests, SpannerFixture.SkipCmekBackupSamplesMessage);
+ // Create a backup with custom encryption keys.
+ var sample = new CreateBackupWithMrCmekAsyncSample();
+ var backup = await sample.CreateBackupWithMrCmekAsync(_fixture.ProjectId, _fixture.InstanceId, _fixture.FixedMrCmekDatabaseId, _fixture.MrCmekBackupId, _fixture.KmsKeyNames);
+ Assert.All(backup.EncryptionInformation, encryptionInfo => _fixture.KmsKeyNames.Contains(CryptoKeyName.Parse(encryptionInfo.KmsKeyVersionAsCryptoKeyVersionName.CryptoKeyId)));
+ }
+}
diff --git a/spanner/api/Spanner.Samples.Tests/CreateDatabaseWithMrCmekAsyncTest.cs b/spanner/api/Spanner.Samples.Tests/CreateDatabaseWithMrCmekAsyncTest.cs
new file mode 100644
index 00000000000..690e9e42929
--- /dev/null
+++ b/spanner/api/Spanner.Samples.Tests/CreateDatabaseWithMrCmekAsyncTest.cs
@@ -0,0 +1,41 @@
+// Copyright 2024 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.Threading.Tasks;
+using Xunit;
+using Google.Cloud.Spanner.Admin.Database.V1;
+using System.Linq;
+
+///
+/// Tests creating a databases using MR CMEK.
+///
+[Collection(nameof(SpannerFixture))]
+public class CreateDatabaseWithMrCmekAsyncTest
+{
+ private readonly SpannerFixture _fixture;
+
+ public CreateDatabaseWithMrCmekAsyncTest(SpannerFixture fixture)
+ {
+ _fixture = fixture;
+ }
+
+ [Fact]
+ public async Task TestCreateDatabaseWithMrCmekAsync()
+ {
+ // Create a database with custom encryption keys.
+ var sample = new CreateDatabaseWithMrCmekAsyncSample();
+ var database = await sample.CreateDatabaseWithMrCmekAsync(_fixture.ProjectId, _fixture.InstanceId, _fixture.MrCmekDatabaseId, _fixture.KmsKeyNames);
+ Assert.All(database.EncryptionConfig.KmsKeyNames, keyName => _fixture.KmsKeyNames.Contains(CryptoKeyName.Parse(keyName)));
+ }
+}
diff --git a/spanner/api/Spanner.Samples.Tests/RestoreDatabaseWithMrCmekAsyncTest.cs b/spanner/api/Spanner.Samples.Tests/RestoreDatabaseWithMrCmekAsyncTest.cs
new file mode 100644
index 00000000000..b50133a2901
--- /dev/null
+++ b/spanner/api/Spanner.Samples.Tests/RestoreDatabaseWithMrCmekAsyncTest.cs
@@ -0,0 +1,42 @@
+// Copyright 2024 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.Spanner.Admin.Database.V1;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+///
+/// Tests restoring a databases using customer managed encryption.
+///
+[Collection(nameof(SpannerFixture))]
+public class RestoreDatabaseWithMrCmekAsyncTest
+{
+ private readonly SpannerFixture _fixture;
+
+ public RestoreDatabaseWithMrCmekAsyncTest(SpannerFixture fixture)
+ {
+ _fixture = fixture;
+ }
+
+ [SkippableFact]
+ public async Task TestRestoreDatabaseWithMrCmekAsync()
+ {
+ Skip.If(!_fixture.RunCmekBackupSampleTests, SpannerFixture.SkipCmekBackupSamplesMessage);
+ var sample = new RestoreDatabaseWithMrCmekAsyncSample();
+ var database = await sample.RestoreDatabaseWithMrCmekAsync(_fixture.ProjectId, _fixture.InstanceId, _fixture.MrCmekRestoreDatabaseId, _fixture.FixedMrCmekBackupId, _fixture.KmsKeyNames);
+ Assert.All(database.EncryptionConfig.KmsKeyNames, keyName => _fixture.KmsKeyNames.Contains(CryptoKeyName.Parse(keyName)));
+ }
+}
diff --git a/spanner/api/Spanner.Samples.Tests/SpannerFixture.cs b/spanner/api/Spanner.Samples.Tests/SpannerFixture.cs
index 0ba576a897f..0c41b89783d 100644
--- a/spanner/api/Spanner.Samples.Tests/SpannerFixture.cs
+++ b/spanner/api/Spanner.Samples.Tests/SpannerFixture.cs
@@ -72,11 +72,21 @@ AlbumTitle STRING(MAX)
public string FixedEncryptedDatabaseId { get; } = "fixed-enc-backup-db";
public string FixedEncryptedBackupId { get; } = "fixed-enc-backup";
+ public string MrCmekDatabaseId { get; private set; }
+ public string MrCmekBackupId { get; } = GenerateId("my-mr-backup-");
+ // 'restore' is abbreviated to prevent the name from becoming longer than 30 characters.
+ public string MrCmekRestoreDatabaseId { get; private set; }
+
+ // These are intentionally kept on the instance to avoid the need to create a new encrypted database and backup for each run.
+ public string FixedMrCmekDatabaseId { get; } = "fixed-mr-backup-db";
+ public string FixedMrCmekBackupId { get; } = "fixed-mr-backup";
+
public CryptoKeyName KmsKeyName { get; } = new CryptoKeyName(
Environment.GetEnvironmentVariable("spanner.test.key.project") ?? Environment.GetEnvironmentVariable("GOOGLE_PROJECT_ID"),
Environment.GetEnvironmentVariable("spanner.test.key.location") ?? "us-central1",
Environment.GetEnvironmentVariable("spanner.test.key.ring") ?? "spanner-test-keyring",
Environment.GetEnvironmentVariable("spanner.test.key.name") ?? "spanner-test-key");
+ public CryptoKeyName[] KmsKeyNames { get; private set; }
public string InstanceIdWithProcessingUnits { get; } = GenerateId("my-ins-pu-");
public string InstanceIdWithMultiRegion { get; } = GenerateId("my-ins-mr-");
@@ -103,6 +113,8 @@ public async Task InitializeAsync()
RestoredDatabaseId = GenerateTempDatabaseId("my-restore-db-");
EncryptedDatabaseId = GenerateTempDatabaseId("my-enc-db-");
EncryptedRestoreDatabaseId = GenerateTempDatabaseId("my-enc-r-db-");
+ MrCmekDatabaseId = GenerateTempDatabaseId("my-mr-db-");
+ MrCmekRestoreDatabaseId = GenerateTempDatabaseId("my-mr-r-db-");
DatabaseAdminClient = await DatabaseAdminClient.CreateAsync();
InstanceAdminClient = await InstanceAdminClient.CreateAsync();
@@ -124,6 +136,7 @@ public async Task InitializeAsync()
if (RunCmekBackupSampleTests)
{
await InitializeEncryptedBackupAsync();
+ await InitializeMrCmekBackupAsync();
}
}
@@ -137,7 +150,8 @@ public async Task DisposeAsync()
DeleteInstanceAsync(InstanceIdWithProcessingUnits),
DeleteInstanceAsync(InstanceIdWithInstancePartition),
DeleteBackupAsync(ToBeCancelledBackupId),
- DeleteBackupAsync(EncryptedBackupId)
+ DeleteBackupAsync(EncryptedBackupId),
+ DeleteBackupAsync(MrCmekBackupId),
};
DeleteInstanceConfig(CreateCustomInstanceConfigId);
@@ -367,6 +381,38 @@ private async Task InitializeEncryptedBackupAsync()
}
}
+ private async Task InitializeMrCmekBackupAsync()
+ {
+ // Sample backup for MR CMEK restore test.
+ try
+ {
+ CreateDatabaseWithMrCmekAsyncSample createDatabaseAsyncSample = new CreateDatabaseWithMrCmekAsyncSample();
+ InsertDataAsyncSample insertDataAsyncSample = new InsertDataAsyncSample();
+ await createDatabaseAsyncSample.CreateDatabaseWithMrCmekAsync(ProjectId, InstanceId, FixedMrCmekDatabaseId, KmsKeyNames);
+ await insertDataAsyncSample.InsertDataAsync(ProjectId, InstanceId, FixedMrCmekDatabaseId);
+ }
+ catch (Exception e) when (e.ToString().Contains("Database already exists"))
+ {
+ // We intentionally keep an existing database around to reduce
+ // the likelihood of test timeouts when creating a backup so
+ // it's ok to get an AlreadyExists error.
+ Console.WriteLine($"Database {FixedMrCmekDatabaseId} already exists.");
+ }
+
+ try
+ {
+ CreateBackupWithMrCmekAsyncSample createBackupSample = new CreateBackupWithMrCmekAsyncSample();
+ await createBackupSample.CreateBackupWithMrCmekAsync(ProjectId, InstanceId, FixedMrCmekDatabaseId, FixedMrCmekBackupId, KmsKeyNames);
+ }
+ catch (RpcException e) when (e.StatusCode == StatusCode.AlreadyExists)
+ {
+ // We intentionally keep an existing backup around to reduce
+ // the likelihood of test timeouts when creating a backup so
+ // it's ok to get an AlreadyExists error.
+ Console.WriteLine($"Backup {FixedMrCmekBackupId} already exists.");
+ }
+ }
+
private async Task DeleteInstanceAsync(string instanceId)
{
try
diff --git a/spanner/api/Spanner.Samples/CopyBackupWithMrCmekAsync.cs b/spanner/api/Spanner.Samples/CopyBackupWithMrCmekAsync.cs
new file mode 100644
index 00000000000..849a4fa694d
--- /dev/null
+++ b/spanner/api/Spanner.Samples/CopyBackupWithMrCmekAsync.cs
@@ -0,0 +1,69 @@
+// Copyright 2024 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START spanner_copy_backup_with_MR_CMEK]
+
+using Google.Api.Gax;
+using Google.Cloud.Spanner.Admin.Database.V1;
+using Google.Cloud.Spanner.Common.V1;
+using Google.Protobuf.WellKnownTypes;
+using System;
+using System.Threading.Tasks;
+
+public class CopyBackupWithMrCmekAsyncSample
+{
+ public async Task CopyBackupWithMrCmekAsync(string sourceInstanceId, string sourceProjectId, string sourceBackupId,
+ string targetInstanceId, string targetProjectId, string targetBackupId,
+ DateTimeOffset expireTime, CryptoKeyName[] kmsKeyNames)
+ {
+ DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.Create();
+
+ var request = new CopyBackupRequest
+ {
+ SourceBackupAsBackupName = new BackupName(sourceProjectId, sourceInstanceId, sourceBackupId),
+ ParentAsInstanceName = new InstanceName(targetProjectId, targetInstanceId),
+ BackupId = targetBackupId,
+ ExpireTime = Timestamp.FromDateTimeOffset(expireTime),
+ EncryptionConfig = new CopyBackupEncryptionConfig
+ {
+ EncryptionType = CopyBackupEncryptionConfig.Types.EncryptionType.CustomerManagedEncryption,
+ KmsKeyNamesAsCryptoKeyNames = { kmsKeyNames },
+ }
+ };
+
+ // Execute the CopyBackup request.
+ var operation = await databaseAdminClient.CopyBackupAsync(request);
+
+ Console.WriteLine("Waiting for the operation to finish.");
+
+ // Poll until the returned long-running operation is complete.
+ var completedResponse = await operation.PollUntilCompletedAsync();
+
+ if (completedResponse.IsFaulted)
+ {
+ Console.WriteLine($"Error while copying backup: {completedResponse.Exception}");
+ throw completedResponse.Exception;
+ }
+
+ Backup backup = completedResponse.Result;
+
+ Console.WriteLine($"Backup copied successfully.");
+ Console.WriteLine($"Backup with Id {sourceBackupId} has been copied from {sourceProjectId}/{sourceInstanceId} to {targetProjectId}/{targetInstanceId} Backup {targetBackupId}");
+ Console.WriteLine($"Backup {backup.Name} of size {backup.SizeBytes} bytes was created with encryption keys {string.Join(", ", (object[]) kmsKeyNames)} at {backup.CreateTime} from {backup.Database} and is in state {backup.State} and has version time {backup.VersionTime.ToDateTime()}");
+
+ return backup;
+ }
+}
+
+// [END spanner_copy_backup_with_MR_CMEK]
diff --git a/spanner/api/Spanner.Samples/CreateBackupWithMrCmekAsync.cs b/spanner/api/Spanner.Samples/CreateBackupWithMrCmekAsync.cs
new file mode 100644
index 00000000000..64bf08b81be
--- /dev/null
+++ b/spanner/api/Spanner.Samples/CreateBackupWithMrCmekAsync.cs
@@ -0,0 +1,64 @@
+// Copyright 2024 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START spanner_create_backup_with_MR_CMEK]
+
+using Google.Cloud.Spanner.Admin.Database.V1;
+using Google.Cloud.Spanner.Common.V1;
+using Google.Protobuf.WellKnownTypes;
+using System;
+using System.Threading.Tasks;
+
+public class CreateBackupWithMrCmekAsyncSample
+{
+ public async Task CreateBackupWithMrCmekAsync(string projectId, string instanceId, string databaseId, string backupId, CryptoKeyName[] kmsKeyNames)
+ {
+ // Create a DatabaseAdminClient instance.
+ DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.Create();
+
+ // Create the CreateBackupRequest with encryption configuration.
+ CreateBackupRequest request = new CreateBackupRequest
+ {
+ ParentAsInstanceName = InstanceName.FromProjectInstance(projectId, instanceId),
+ BackupId = backupId,
+ Backup = new Backup
+ {
+ DatabaseAsDatabaseName = DatabaseName.FromProjectInstanceDatabase(projectId, instanceId, databaseId),
+ ExpireTime = DateTime.UtcNow.AddDays(14).ToTimestamp(),
+ },
+ EncryptionConfig = new CreateBackupEncryptionConfig
+ {
+ EncryptionType = CreateBackupEncryptionConfig.Types.EncryptionType.CustomerManagedEncryption,
+ KmsKeyNamesAsCryptoKeyNames = { kmsKeyNames },
+ },
+ };
+ // Execute the CreateBackup request.
+ var operation = await databaseAdminClient.CreateBackupAsync(request);
+
+ Console.WriteLine("Waiting for the operation to finish.");
+
+ // Poll until the returned long-running operation is complete.
+ var completedResponse = await operation.PollUntilCompletedAsync();
+ if (completedResponse.IsFaulted)
+ {
+ Console.WriteLine($"Error while creating backup: {completedResponse.Exception}");
+ throw completedResponse.Exception;
+ }
+
+ var backup = completedResponse.Result;
+ Console.WriteLine($"Backup {backup.Name} of size {backup.SizeBytes} bytes was created with encryption keys {string.Join(", ", (object[]) kmsKeyNames)} at {backup.CreateTime}");
+ return backup;
+ }
+}
+// [END spanner_create_backup_with_MR_CMEK]
diff --git a/spanner/api/Spanner.Samples/CreateDatabaseWithMrCmekAsync.cs b/spanner/api/Spanner.Samples/CreateDatabaseWithMrCmekAsync.cs
new file mode 100644
index 00000000000..4f00238e017
--- /dev/null
+++ b/spanner/api/Spanner.Samples/CreateDatabaseWithMrCmekAsync.cs
@@ -0,0 +1,74 @@
+// Copyright 2024 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START spanner_create_database_with_MR_CMEK]
+
+using Google.Cloud.Spanner.Admin.Database.V1;
+using Google.Cloud.Spanner.Common.V1;
+using System;
+using System.Threading.Tasks;
+
+public class CreateDatabaseWithMrCmekAsyncSample
+{
+ public async Task CreateDatabaseWithMrCmekAsync(string projectId, string instanceId, string databaseId, CryptoKeyName[] kmsKeyNames)
+ {
+ // Create a DatabaseAdminClient instance that can be used to execute a
+ // CreateDatabaseRequest with custom encryption configuration options.
+ DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.Create();
+ // Define create table statement for table #1.
+ var createSingersTable =
+ @"CREATE TABLE Singers (
+ SingerId INT64 NOT NULL,
+ FirstName STRING(1024),
+ LastName STRING(1024),
+ ComposerInfo BYTES(MAX)
+ ) PRIMARY KEY (SingerId)";
+ // Define create table statement for table #2.
+ var createAlbumsTable =
+ @"CREATE TABLE Albums (
+ SingerId INT64 NOT NULL,
+ AlbumId INT64 NOT NULL,
+ AlbumTitle STRING(MAX)
+ ) PRIMARY KEY (SingerId, AlbumId),
+ INTERLEAVE IN PARENT Singers ON DELETE CASCADE";
+
+ // Create the CreateDatabase request with encryption configuration and execute it.
+ var request = new CreateDatabaseRequest
+ {
+ ParentAsInstanceName = InstanceName.FromProjectInstance(projectId, instanceId),
+ CreateStatement = $"CREATE DATABASE `{databaseId}`",
+ ExtraStatements = { createSingersTable, createAlbumsTable },
+ EncryptionConfig = new EncryptionConfig
+ {
+ KmsKeyNamesAsCryptoKeyNames = { kmsKeyNames },
+ },
+ };
+ var operation = await databaseAdminClient.CreateDatabaseAsync(request);
+
+ // Wait until the operation has finished.
+ Console.WriteLine("Waiting for the operation to finish.");
+ var completedResponse = await operation.PollUntilCompletedAsync();
+ if (completedResponse.IsFaulted)
+ {
+ Console.WriteLine($"Error while creating database: {completedResponse.Exception}");
+ throw completedResponse.Exception;
+ }
+
+ var database = completedResponse.Result;
+ Console.WriteLine($"Database {database.Name} created with encryption keys {string.Join(", ", (object[]) kmsKeyNames)}");
+
+ return database;
+ }
+}
+// [END spanner_create_database_with_MR_CMEK]
diff --git a/spanner/api/Spanner.Samples/RestoreDatabaseWithMrCmekAsync.cs b/spanner/api/Spanner.Samples/RestoreDatabaseWithMrCmekAsync.cs
new file mode 100644
index 00000000000..889f4a5a8ed
--- /dev/null
+++ b/spanner/api/Spanner.Samples/RestoreDatabaseWithMrCmekAsync.cs
@@ -0,0 +1,60 @@
+// Copyright 2024 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START spanner_restore_backup_with_MR_CMEK]
+
+using Google.Cloud.Spanner.Admin.Database.V1;
+using Google.Cloud.Spanner.Common.V1;
+using System;
+using System.Threading.Tasks;
+
+public class RestoreDatabaseWithMrCmekAsyncSample
+{
+ public async Task RestoreDatabaseWithMrCmekAsync(string projectId, string instanceId, string databaseId, string backupId, CryptoKeyName[] kmsKeyNames)
+ {
+ // Create a DatabaseAdminClient instance.
+ DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.Create();
+
+ // Create the RestoreDatabaseRequest with encryption configuration.
+ RestoreDatabaseRequest request = new RestoreDatabaseRequest
+ {
+ ParentAsInstanceName = InstanceName.FromProjectInstance(projectId, instanceId),
+ DatabaseId = databaseId,
+ BackupAsBackupName = BackupName.FromProjectInstanceBackup(projectId, instanceId, backupId),
+ EncryptionConfig = new RestoreDatabaseEncryptionConfig
+ {
+ EncryptionType = RestoreDatabaseEncryptionConfig.Types.EncryptionType.CustomerManagedEncryption,
+ KmsKeyNamesAsCryptoKeyNames = { kmsKeyNames },
+ }
+ };
+ // Execute the RestoreDatabase request.
+ var operation = await databaseAdminClient.RestoreDatabaseAsync(request);
+
+ Console.WriteLine("Waiting for the operation to finish.");
+
+ // Poll until the returned long-running operation is complete.
+ var completedResponse = await operation.PollUntilCompletedAsync();
+ if (completedResponse.IsFaulted)
+ {
+ Console.WriteLine($"Error while restoring database: {completedResponse.Exception}");
+ throw completedResponse.Exception;
+ }
+
+ var database = completedResponse.Result;
+ var restoreInfo = database.RestoreInfo;
+ Console.WriteLine($"Database {restoreInfo.BackupInfo.SourceDatabase} restored to {database.Name} from backup {restoreInfo.BackupInfo.Backup} using encryption keys {string.Join(", ", (object[]) kmsKeyNames)}");
+ return database;
+ }
+}
+// [END spanner_restore_backup_with_MR_CMEK]