From dff3292515f22dfa0bbf14e2e5c66875424e032b Mon Sep 17 00:00:00 2001 From: Htut Khine Htay Win Date: Wed, 28 Aug 2024 00:31:54 +0000 Subject: [PATCH 1/2] feat: allow multiple KMS keys to create CMEK database/backup --- google/cloud/spanner_v1/backup.py | 14 +++- google/cloud/spanner_v1/database.py | 9 ++- samples/samples/backup_sample.py | 93 +++++++++++++++++++++++++++ samples/samples/backup_sample_test.py | 34 ++++++++++ samples/samples/snippets.py | 43 +++++++++++++ samples/samples/snippets_test.py | 9 +++ tests/unit/test_database.py | 12 ++++ 7 files changed, 209 insertions(+), 5 deletions(-) diff --git a/google/cloud/spanner_v1/backup.py b/google/cloud/spanner_v1/backup.py index 1fcffbe05a..b4d60fa1ea 100644 --- a/google/cloud/spanner_v1/backup.py +++ b/google/cloud/spanner_v1/backup.py @@ -280,16 +280,24 @@ def create(self): if ( ( self._encryption_config - and self._encryption_config.kms_key_name + and ( + self._encryption_config.kms_key_name + or self._encryption_config.kms_key_names + ) and self._encryption_config.encryption_type != CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION ) and self._encryption_config - and self._encryption_config.kms_key_name + and ( + self._encryption_config.kms_key_name + or self._encryption_config.kms_key_names + ) and self._encryption_config.encryption_type != CopyBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION ): - raise ValueError("kms_key_name only used with CUSTOMER_MANAGED_ENCRYPTION") + raise ValueError( + "kms_key_name or kms_key_names only used with CUSTOMER_MANAGED_ENCRYPTION" + ) api = self._instance._client.database_admin_api metadata = _metadata_with_prefix(self.name) diff --git a/google/cloud/spanner_v1/database.py b/google/cloud/spanner_v1/database.py index 6bd4f3703e..82f8349540 100644 --- a/google/cloud/spanner_v1/database.py +++ b/google/cloud/spanner_v1/database.py @@ -911,11 +911,16 @@ def restore(self, source): ) if ( self.encryption_config - and self.encryption_config.kms_key_name + and ( + self.encryption_config.kms_key_name + or self.encryption_config.kms_key_names + ) and self.encryption_config.encryption_type != RestoreDatabaseEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION ): - raise ValueError("kms_key_name only used with CUSTOMER_MANAGED_ENCRYPTION") + raise ValueError( + "kms_key_name or kms_key_names only used with CUSTOMER_MANAGED_ENCRYPTION" + ) api = self._instance._client.database_admin_api metadata = _metadata_with_prefix(self.name) request = RestoreDatabaseRequest( diff --git a/samples/samples/backup_sample.py b/samples/samples/backup_sample.py index d3c2c667c5..55752d03f7 100644 --- a/samples/samples/backup_sample.py +++ b/samples/samples/backup_sample.py @@ -119,6 +119,55 @@ def create_backup_with_encryption_key( # [END spanner_create_backup_with_encryption_key] +# [START spanner_create_backup_with_MR_CMEK] +def create_backup_with_multiple_kms_keys( + instance_id, database_id, backup_id, kms_key_names +): + """Creates a backup for a database using multiple KMS keys(CMEK).""" + + from google.cloud.spanner_admin_database_v1 import \ + CreateBackupEncryptionConfig + from google.cloud.spanner_admin_database_v1.types import \ + backup as backup_pb + + spanner_client = spanner.Client() + database_admin_api = spanner_client.database_admin_api + + # Create a backup + expire_time = datetime.utcnow() + timedelta(days=14) + encryption_config = { + "encryption_type": CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION, + "kms_key_names": kms_key_names, + } + request = backup_pb.CreateBackupRequest( + parent=database_admin_api.instance_path(spanner_client.project, instance_id), + backup_id=backup_id, + backup=backup_pb.Backup( + database=database_admin_api.database_path( + spanner_client.project, instance_id, database_id + ), + expire_time=expire_time, + ), + encryption_config=encryption_config, + ) + operation = database_admin_api.create_backup(request) + + # Wait for backup operation to complete. + backup = operation.result(2100) + + # Verify that the backup is ready. + assert backup.state == backup_pb.Backup.State.READY + + # Get the name, create time, backup size and encryption key. + print( + "Backup {} of size {} bytes was created at {} using encryption key {}".format( + backup.name, backup.size_bytes, backup.create_time, kms_key_names + ) + ) + + +# [END spanner_create_backup_with_MR_CMEK] + # [START spanner_restore_backup] def restore_database(instance_id, new_database_id, backup_id): @@ -200,6 +249,50 @@ def restore_database_with_encryption_key( # [END spanner_restore_backup_with_encryption_key] +# [START spanner_restore_backup_with_MR_CMEK] +def restore_database_with_multiple_kms_keys( + instance_id, new_database_id, backup_id, kms_key_names +): + """Restores a database from a backup using a Customer Managed Encryption Key (CMEK).""" + from google.cloud.spanner_admin_database_v1 import ( + RestoreDatabaseEncryptionConfig, RestoreDatabaseRequest) + + spanner_client = spanner.Client() + database_admin_api = spanner_client.database_admin_api + + # Start restoring an existing backup to a new database. + encryption_config = { + "encryption_type": RestoreDatabaseEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION, + "kms_key_names": kms_key_names, + } + + request = RestoreDatabaseRequest( + parent=database_admin_api.instance_path(spanner_client.project, instance_id), + database_id=new_database_id, + backup=database_admin_api.backup_path( + spanner_client.project, instance_id, backup_id + ), + encryption_config=encryption_config, + ) + operation = database_admin_api.restore_database(request) + + # Wait for restore operation to complete. + db = operation.result(1600) + + # Newly created database has restore information. + restore_info = db.restore_info + print( + "Database {} restored to {} from backup {} with using encryption key {}.".format( + restore_info.backup_info.source_database, + new_database_id, + restore_info.backup_info.backup, + db.encryption_config.kms_key_names, + ) + ) + + +# [END spanner_restore_backup_with_MR_CMEK] + # [START spanner_cancel_backup_create] def cancel_backup(instance_id, database_id, backup_id): diff --git a/samples/samples/backup_sample_test.py b/samples/samples/backup_sample_test.py index 6d656c5545..fe91556d95 100644 --- a/samples/samples/backup_sample_test.py +++ b/samples/samples/backup_sample_test.py @@ -92,6 +92,23 @@ def test_create_backup_with_encryption_key( assert CMEK_BACKUP_ID in out assert kms_key_name in out +@pytest.mark.dependency(name="create_backup_with_multiple_kms_keys") +def test_create_backup_with_multiple_kms_keys( + capsys, + instance_id, + sample_database, + kms_key_names, +): + backup_sample.create_backup_with_multiple_kms_keys( + instance_id, + sample_database.database_id, + CMEK_BACKUP_ID, + kms_key_names, + ) + out, _ = capsys.readouterr() + assert CMEK_BACKUP_ID in out + assert kms_key_names in out + @pytest.mark.dependency(depends=["create_backup"]) @RetryErrors(exception=DeadlineExceeded, max_tries=2) @@ -120,6 +137,23 @@ def test_restore_database_with_encryption_key( assert CMEK_BACKUP_ID in out assert kms_key_name in out +@pytest.mark.dependency(depends=["restore_database_with_multiple_kms_keys"]) +@RetryErrors(exception=DeadlineExceeded, max_tries=2) +def test_restore_database_with_multiple_kms_keys( + capsys, + instance_id, + sample_database, + kms_key_names, +): + backup_sample.restore_database_with_multiple_kms_keys( + instance_id, CMEK_RESTORE_DB_ID, CMEK_BACKUP_ID, kms_key_name + ) + out, _ = capsys.readouterr() + assert (sample_database.database_id + " restored to ") in out + assert (CMEK_RESTORE_DB_ID + " from backup ") in out + assert CMEK_BACKUP_ID in out + assert kms_key_names in out + @pytest.mark.dependency(depends=["create_backup", "copy_backup"]) def test_list_backup_operations(capsys, instance_id, sample_database): diff --git a/samples/samples/snippets.py b/samples/samples/snippets.py index 93c8de4148..63755a26fa 100644 --- a/samples/samples/snippets.py +++ b/samples/samples/snippets.py @@ -341,6 +341,49 @@ def create_database_with_encryption_key(instance_id, database_id, kms_key_name): # [END spanner_create_database_with_encryption_key] +# [START spanner_create_database_with_MR_CMEK] +def spanner_create_database_with_multiple_kms_keys(instance_id, database_id, kms_key_names): + """Creates a database with tables using multiple KMS keys(CMEK).""" + from google.cloud.spanner_admin_database_v1 import EncryptionConfig + from google.cloud.spanner_admin_database_v1.types import \ + spanner_database_admin + + spanner_client = spanner.Client() + database_admin_api = spanner_client.database_admin_api + + request = spanner_database_admin.CreateDatabaseRequest( + parent=database_admin_api.instance_path(spanner_client.project, instance_id), + create_statement=f"CREATE DATABASE `{database_id}`", + extra_statements=[ + """CREATE TABLE Singers ( + SingerId INT64 NOT NULL, + FirstName STRING(1024), + LastName STRING(1024), + SingerInfo BYTES(MAX) + ) PRIMARY KEY (SingerId)""", + """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""", + ], + encryption_config=EncryptionConfig(kms_key_names=kms_key_names), + ) + + operation = database_admin_api.create_database(request=request) + + print("Waiting for operation to complete...") + database = operation.result(OPERATION_TIMEOUT_SECONDS) + + print( + "Database {} created with multiple KMS keys {}".format( + database.name, database.encryption_config.kms_key_names + ) + ) + + +# [END spanner_create_database_with_MR_CMEK] # [START spanner_create_database_with_default_leader] def create_database_with_default_leader(instance_id, database_id, default_leader): diff --git a/samples/samples/snippets_test.py b/samples/samples/snippets_test.py index 6657703fd1..0d05e681d5 100644 --- a/samples/samples/snippets_test.py +++ b/samples/samples/snippets_test.py @@ -227,6 +227,15 @@ def test_create_database_with_encryption_config( assert cmek_database_id in out assert kms_key_name in out +def test_spanner_create_database_with_multiple_kms_keys( + capsys, instance_id, cmek_database_id, kms_key_names +): + snippets.create_database_with_encryption_key( + instance_id, cmek_database_id, kms_key_names + ) + out, _ = capsys.readouterr() + assert cmek_database_id in out + assert kms_key_names in out def test_get_instance_config(capsys): instance_config = "nam6" diff --git a/tests/unit/test_database.py b/tests/unit/test_database.py index 90fa0c269f..d978f17cd9 100644 --- a/tests/unit/test_database.py +++ b/tests/unit/test_database.py @@ -210,6 +210,18 @@ def test_ctor_w_encryption_config(self): self.assertIs(database._instance, instance) self.assertEqual(database._encryption_config, encryption_config) + def test_ctor_w_multiple_kms_keys(self): + from google.cloud.spanner_admin_database_v1 import EncryptionConfig + + instance = _Instance(self.INSTANCE_NAME) + encryption_config = EncryptionConfig(kms_key_names=["kms_key1", "kms_key2"]) + database = self._make_one( + self.DATABASE_ID, instance, encryption_config=encryption_config + ) + self.assertEqual(database.database_id, self.DATABASE_ID) + self.assertIs(database._instance, instance) + self.assertEqual(database._encryption_config, encryption_config) + def test_ctor_w_directed_read_options(self): client = _Client(directed_read_options=DIRECTED_READ_OPTIONS) instance = _Instance(self.INSTANCE_NAME, client=client) From b97fa857ae56064fecc74fee2986fdc697edd942 Mon Sep 17 00:00:00 2001 From: Htut Khine Htay Win Date: Thu, 5 Sep 2024 00:29:10 +0000 Subject: [PATCH 2/2] Removed api files --- google/cloud/spanner_v1/backup.py | 14 ++- google/cloud/spanner_v1/database.py | 9 +- samples/samples/backup_sample.py | 133 ++++++++++++++++++++++++++ samples/samples/backup_sample_test.py | 47 +++++++++ samples/samples/snippets.py | 43 +++++++++ samples/samples/snippets_test.py | 9 ++ tests/unit/test_database.py | 12 +++ 7 files changed, 262 insertions(+), 5 deletions(-) diff --git a/google/cloud/spanner_v1/backup.py b/google/cloud/spanner_v1/backup.py index 1fcffbe05a..b4d60fa1ea 100644 --- a/google/cloud/spanner_v1/backup.py +++ b/google/cloud/spanner_v1/backup.py @@ -280,16 +280,24 @@ def create(self): if ( ( self._encryption_config - and self._encryption_config.kms_key_name + and ( + self._encryption_config.kms_key_name + or self._encryption_config.kms_key_names + ) and self._encryption_config.encryption_type != CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION ) and self._encryption_config - and self._encryption_config.kms_key_name + and ( + self._encryption_config.kms_key_name + or self._encryption_config.kms_key_names + ) and self._encryption_config.encryption_type != CopyBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION ): - raise ValueError("kms_key_name only used with CUSTOMER_MANAGED_ENCRYPTION") + raise ValueError( + "kms_key_name or kms_key_names only used with CUSTOMER_MANAGED_ENCRYPTION" + ) api = self._instance._client.database_admin_api metadata = _metadata_with_prefix(self.name) diff --git a/google/cloud/spanner_v1/database.py b/google/cloud/spanner_v1/database.py index 6bd4f3703e..82f8349540 100644 --- a/google/cloud/spanner_v1/database.py +++ b/google/cloud/spanner_v1/database.py @@ -911,11 +911,16 @@ def restore(self, source): ) if ( self.encryption_config - and self.encryption_config.kms_key_name + and ( + self.encryption_config.kms_key_name + or self.encryption_config.kms_key_names + ) and self.encryption_config.encryption_type != RestoreDatabaseEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION ): - raise ValueError("kms_key_name only used with CUSTOMER_MANAGED_ENCRYPTION") + raise ValueError( + "kms_key_name or kms_key_names only used with CUSTOMER_MANAGED_ENCRYPTION" + ) api = self._instance._client.database_admin_api metadata = _metadata_with_prefix(self.name) request = RestoreDatabaseRequest( diff --git a/samples/samples/backup_sample.py b/samples/samples/backup_sample.py index d3c2c667c5..89afe9fabc 100644 --- a/samples/samples/backup_sample.py +++ b/samples/samples/backup_sample.py @@ -119,6 +119,55 @@ def create_backup_with_encryption_key( # [END spanner_create_backup_with_encryption_key] +# [START spanner_create_backup_with_MR_CMEK] +def create_backup_with_multiple_kms_keys( + instance_id, database_id, backup_id, kms_key_names +): + """Creates a backup for a database using multiple KMS keys(CMEK).""" + + from google.cloud.spanner_admin_database_v1 import \ + CreateBackupEncryptionConfig + from google.cloud.spanner_admin_database_v1.types import \ + backup as backup_pb + + spanner_client = spanner.Client() + database_admin_api = spanner_client.database_admin_api + + # Create a backup + expire_time = datetime.utcnow() + timedelta(days=14) + encryption_config = { + "encryption_type": CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION, + "kms_key_names": kms_key_names, + } + request = backup_pb.CreateBackupRequest( + parent=database_admin_api.instance_path(spanner_client.project, instance_id), + backup_id=backup_id, + backup=backup_pb.Backup( + database=database_admin_api.database_path( + spanner_client.project, instance_id, database_id + ), + expire_time=expire_time, + ), + encryption_config=encryption_config, + ) + operation = database_admin_api.create_backup(request) + + # Wait for backup operation to complete. + backup = operation.result(2100) + + # Verify that the backup is ready. + assert backup.state == backup_pb.Backup.State.READY + + # Get the name, create time, backup size and encryption key. + print( + "Backup {} of size {} bytes was created at {} using encryption key {}".format( + backup.name, backup.size_bytes, backup.create_time, kms_key_names + ) + ) + + +# [END spanner_create_backup_with_MR_CMEK] + # [START spanner_restore_backup] def restore_database(instance_id, new_database_id, backup_id): @@ -200,6 +249,50 @@ def restore_database_with_encryption_key( # [END spanner_restore_backup_with_encryption_key] +# [START spanner_restore_backup_with_MR_CMEK] +def restore_database_with_multiple_kms_keys( + instance_id, new_database_id, backup_id, kms_key_names +): + """Restores a database from a backup using a Customer Managed Encryption Key (CMEK).""" + from google.cloud.spanner_admin_database_v1 import ( + RestoreDatabaseEncryptionConfig, RestoreDatabaseRequest) + + spanner_client = spanner.Client() + database_admin_api = spanner_client.database_admin_api + + # Start restoring an existing backup to a new database. + encryption_config = { + "encryption_type": RestoreDatabaseEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION, + "kms_key_names": kms_key_names, + } + + request = RestoreDatabaseRequest( + parent=database_admin_api.instance_path(spanner_client.project, instance_id), + database_id=new_database_id, + backup=database_admin_api.backup_path( + spanner_client.project, instance_id, backup_id + ), + encryption_config=encryption_config, + ) + operation = database_admin_api.restore_database(request) + + # Wait for restore operation to complete. + db = operation.result(1600) + + # Newly created database has restore information. + restore_info = db.restore_info + print( + "Database {} restored to {} from backup {} with using encryption key {}.".format( + restore_info.backup_info.source_database, + new_database_id, + restore_info.backup_info.backup, + db.encryption_config.kms_key_names, + ) + ) + + +# [END spanner_restore_backup_with_MR_CMEK] + # [START spanner_cancel_backup_create] def cancel_backup(instance_id, database_id, backup_id): @@ -613,6 +706,46 @@ def copy_backup(instance_id, backup_id, source_backup_path): # [END spanner_copy_backup] +# [START spanner_copy_backup_with_MR_CMEK] +def copy_backup_with_multiple_kms_keys(instance_id, backup_id, source_backup_path, kms_key_names): + """Copies a backup.""" + + from google.cloud.spanner_admin_database_v1.types import \ + backup as backup_pb + + spanner_client = spanner.Client() + database_admin_api = spanner_client.database_admin_api + + # Create a backup object and wait for copy backup operation to complete. + expire_time = datetime.utcnow() + timedelta(days=14) + request = backup_pb.CopyBackupRequest( + parent=database_admin_api.instance_path(spanner_client.project, instance_id), + backup_id=backup_id, + source_backup=source_backup_path, + expire_time=expire_time, + kms_key_names=kms_key_names + ) + + operation = database_admin_api.copy_backup(request) + + # Wait for backup operation to complete. + copy_backup = operation.result(2100) + + # Verify that the copy backup is ready. + assert copy_backup.state == backup_pb.Backup.State.READY + + print( + "Backup {} of size {} bytes was created at {} with version time {}".format( + copy_backup.name, + copy_backup.size_bytes, + copy_backup.create_time, + copy_backup.version_time, + ) + ) + + +# [END spanner_copy_backup_with_MR_CMEK] + if __name__ == "__main__": # noqa: C901 parser = argparse.ArgumentParser( diff --git a/samples/samples/backup_sample_test.py b/samples/samples/backup_sample_test.py index 6d656c5545..9da82e5097 100644 --- a/samples/samples/backup_sample_test.py +++ b/samples/samples/backup_sample_test.py @@ -74,6 +74,19 @@ def test_copy_backup(capsys, instance_id, spanner_client): out, _ = capsys.readouterr() assert COPY_BACKUP_ID in out +@pytest.mark.dependency(name="copy_backup_with_multiple_kms_keys", depends=["create_backup"]) +def test_copy_backup_with_multiple_kms_keys(capsys, instance_id, spanner_client, kms_key_names): + source_backp_path = ( + spanner_client.project_name + + "/instances/" + + instance_id + + "/backups/" + + BACKUP_ID + ) + backup_sample.copy_backup_with_multiple_kms_keys(instance_id, COPY_BACKUP_ID, source_backup_path, kms_key_names) + out, _ = capsys.readouterr() + assert COPY_BACKUP_ID in out + @pytest.mark.dependency(name="create_backup_with_encryption_key") def test_create_backup_with_encryption_key( @@ -92,6 +105,23 @@ def test_create_backup_with_encryption_key( assert CMEK_BACKUP_ID in out assert kms_key_name in out +@pytest.mark.dependency(name="create_backup_with_multiple_kms_keys") +def test_create_backup_with_multiple_kms_keys( + capsys, + instance_id, + sample_database, + kms_key_names, +): + backup_sample.create_backup_with_multiple_kms_keys( + instance_id, + sample_database.database_id, + CMEK_BACKUP_ID, + kms_key_names, + ) + out, _ = capsys.readouterr() + assert CMEK_BACKUP_ID in out + assert kms_key_names in out + @pytest.mark.dependency(depends=["create_backup"]) @RetryErrors(exception=DeadlineExceeded, max_tries=2) @@ -120,6 +150,23 @@ def test_restore_database_with_encryption_key( assert CMEK_BACKUP_ID in out assert kms_key_name in out +@pytest.mark.dependency(depends=["restore_database_with_multiple_kms_keys"]) +@RetryErrors(exception=DeadlineExceeded, max_tries=2) +def test_restore_database_with_multiple_kms_keys( + capsys, + instance_id, + sample_database, + kms_key_names, +): + backup_sample.restore_database_with_multiple_kms_keys( + instance_id, CMEK_RESTORE_DB_ID, CMEK_BACKUP_ID, kms_key_name + ) + out, _ = capsys.readouterr() + assert (sample_database.database_id + " restored to ") in out + assert (CMEK_RESTORE_DB_ID + " from backup ") in out + assert CMEK_BACKUP_ID in out + assert kms_key_names in out + @pytest.mark.dependency(depends=["create_backup", "copy_backup"]) def test_list_backup_operations(capsys, instance_id, sample_database): diff --git a/samples/samples/snippets.py b/samples/samples/snippets.py index 93c8de4148..63755a26fa 100644 --- a/samples/samples/snippets.py +++ b/samples/samples/snippets.py @@ -341,6 +341,49 @@ def create_database_with_encryption_key(instance_id, database_id, kms_key_name): # [END spanner_create_database_with_encryption_key] +# [START spanner_create_database_with_MR_CMEK] +def spanner_create_database_with_multiple_kms_keys(instance_id, database_id, kms_key_names): + """Creates a database with tables using multiple KMS keys(CMEK).""" + from google.cloud.spanner_admin_database_v1 import EncryptionConfig + from google.cloud.spanner_admin_database_v1.types import \ + spanner_database_admin + + spanner_client = spanner.Client() + database_admin_api = spanner_client.database_admin_api + + request = spanner_database_admin.CreateDatabaseRequest( + parent=database_admin_api.instance_path(spanner_client.project, instance_id), + create_statement=f"CREATE DATABASE `{database_id}`", + extra_statements=[ + """CREATE TABLE Singers ( + SingerId INT64 NOT NULL, + FirstName STRING(1024), + LastName STRING(1024), + SingerInfo BYTES(MAX) + ) PRIMARY KEY (SingerId)""", + """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""", + ], + encryption_config=EncryptionConfig(kms_key_names=kms_key_names), + ) + + operation = database_admin_api.create_database(request=request) + + print("Waiting for operation to complete...") + database = operation.result(OPERATION_TIMEOUT_SECONDS) + + print( + "Database {} created with multiple KMS keys {}".format( + database.name, database.encryption_config.kms_key_names + ) + ) + + +# [END spanner_create_database_with_MR_CMEK] # [START spanner_create_database_with_default_leader] def create_database_with_default_leader(instance_id, database_id, default_leader): diff --git a/samples/samples/snippets_test.py b/samples/samples/snippets_test.py index 6657703fd1..0d05e681d5 100644 --- a/samples/samples/snippets_test.py +++ b/samples/samples/snippets_test.py @@ -227,6 +227,15 @@ def test_create_database_with_encryption_config( assert cmek_database_id in out assert kms_key_name in out +def test_spanner_create_database_with_multiple_kms_keys( + capsys, instance_id, cmek_database_id, kms_key_names +): + snippets.create_database_with_encryption_key( + instance_id, cmek_database_id, kms_key_names + ) + out, _ = capsys.readouterr() + assert cmek_database_id in out + assert kms_key_names in out def test_get_instance_config(capsys): instance_config = "nam6" diff --git a/tests/unit/test_database.py b/tests/unit/test_database.py index 90fa0c269f..d978f17cd9 100644 --- a/tests/unit/test_database.py +++ b/tests/unit/test_database.py @@ -210,6 +210,18 @@ def test_ctor_w_encryption_config(self): self.assertIs(database._instance, instance) self.assertEqual(database._encryption_config, encryption_config) + def test_ctor_w_multiple_kms_keys(self): + from google.cloud.spanner_admin_database_v1 import EncryptionConfig + + instance = _Instance(self.INSTANCE_NAME) + encryption_config = EncryptionConfig(kms_key_names=["kms_key1", "kms_key2"]) + database = self._make_one( + self.DATABASE_ID, instance, encryption_config=encryption_config + ) + self.assertEqual(database.database_id, self.DATABASE_ID) + self.assertIs(database._instance, instance) + self.assertEqual(database._encryption_config, encryption_config) + def test_ctor_w_directed_read_options(self): client = _Client(directed_read_options=DIRECTED_READ_OPTIONS) instance = _Instance(self.INSTANCE_NAME, client=client)