Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(default_retention_period): Changes to copy backup path to support null expire time #963

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions google/cloud/spanner_v1/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ class Backup(object):

:type expire_time: :class:`datetime.datetime`
:param expire_time: (Optional) The expire time that will be used to
create the backup. Required if the create method
needs to be called.
create the backup. If the expire time is not specified
then the backup will be retained for the duration of
maximum retention period.

:type version_time: :class:`datetime.datetime`
:param version_time: (Optional) The version time that was specified for
Expand Down Expand Up @@ -271,8 +272,6 @@ def create(self):
:raises BadRequest: if the database or expire_time values are invalid
or expire_time is not set
"""
if not self._expire_time:
raise ValueError("expire_time not set")

if not self._database and not self._source_backup:
raise ValueError("database and source backup both not set")
Expand Down
6 changes: 4 additions & 2 deletions google/cloud/spanner_v1/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,8 @@ def backup(
:type expire_time: :class:`datetime.datetime`
:param expire_time:
Optional. The expire time that will be used when creating the backup.
Required if the create method needs to be called.
If the expire time is not specified then the backup will be retained
for the duration of maximum retention period.

:type version_time: :class:`datetime.datetime`
:param version_time:
Expand Down Expand Up @@ -586,7 +587,8 @@ def copy_backup(
:type expire_time: :class:`datetime.datetime`
:param expire_time:
Optional. The expire time that will be used when creating the copy backup.
Required if the create method needs to be called.
If the expire time is not specified then the backup will be created with
the expiration time of the source backup.
:type encryption_config:
:class:`~google.cloud.spanner_admin_database_v1.types.CopyBackupEncryptionConfig`
or :class:`dict`
Expand Down
179 changes: 175 additions & 4 deletions tests/unit/test_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class _BaseTest(unittest.TestCase):
DATABASE_NAME = INSTANCE_NAME + "/databases/" + DATABASE_ID
BACKUP_ID = "backup_id"
BACKUP_NAME = INSTANCE_NAME + "/backups/" + BACKUP_ID
DST_BACKUP_ID = "dst_backup_id"
DST_BACKUP_NAME = INSTANCE_NAME + "/backups" + DST_BACKUP_ID

def _make_one(self, *args, **kwargs):
return self._get_target_class()(*args, **kwargs)
Expand Down Expand Up @@ -329,11 +331,45 @@ def test_create_instance_not_found(self):
)

def test_create_expire_time_not_set(self):
instance = _Instance(self.INSTANCE_NAME)
backup = self._make_one(self.BACKUP_ID, instance, database=self.DATABASE_NAME)
from google.cloud.spanner_admin_database_v1 import Backup
from google.cloud.spanner_admin_database_v1 import CreateBackupRequest
from datetime import datetime
from datetime import timedelta
from datetime import timezone

with self.assertRaises(ValueError):
backup.create()
op_future = object()
client = _Client()
api = client.database_admin_api = self._make_database_admin_api()
api.create_backup.return_value = op_future

instance = _Instance(self.INSTANCE_NAME, client=client)
version_timestamp = datetime.utcnow() - timedelta(minutes=5)
version_timestamp = version_timestamp.replace(tzinfo=timezone.utc)
backup = self._make_one(
self.BACKUP_ID,
instance,
database=self.DATABASE_NAME,
version_time=version_timestamp,
)

backup_pb = Backup(
database=self.DATABASE_NAME,
version_time=version_timestamp,
)

future = backup.create()
self.assertIs(future, op_future)

request = CreateBackupRequest(
parent=self.INSTANCE_NAME,
backup_id=self.BACKUP_ID,
backup=backup_pb,
)

api.create_backup.assert_called_once_with(
request=request,
metadata=[("google-cloud-resource-prefix", backup.name)],
)

def test_create_database_not_set(self):
instance = _Instance(self.INSTANCE_NAME)
Expand Down Expand Up @@ -413,6 +449,141 @@ def test_create_w_invalid_encryption_config(self):
with self.assertRaises(ValueError):
backup.create()

def test_copy_expire_time_not_set(self):
from google.cloud.spanner_admin_database_v1 import CopyBackupRequest

op_future = object()
client = _Client()
api = client.database_admin_api = self._make_database_admin_api()
api.copy_backup.return_value = op_future
instance = _Instance(self.INSTANCE_NAME, client=client)
backup = self._make_one(
self.DST_BACKUP_ID,
instance,
source_backup=self.BACKUP_ID,
)

future = backup.create()
self.assertIs(future, op_future)

request = CopyBackupRequest(
parent=self.INSTANCE_NAME,
backup_id=self.DST_BACKUP_ID,
source_backup=self.BACKUP_ID,
)

api.copy_backup.assert_called_once_with(
request=request,
metadata=[("google-cloud-resource-prefix", backup.name)],
)

def test_copy_fail_database_and_source_backup_not_set(self):
client = _Client()
client.database_admin_api = self._make_database_admin_api()
instance = _Instance(self.INSTANCE_NAME, client=client)
expire_timestamp = self._make_timestamp()
backup = self._make_one(
self.DST_BACKUP_ID,
instance,
expire_time=expire_timestamp,
)

with self.assertRaises(ValueError):
backup.create()

def test_copy_fail_incompatible_encryption_type(self):
from google.cloud.spanner_admin_database_v1 import CreateBackupEncryptionConfig

client = _Client()
client.database_admin_api = self._make_database_admin_api()
instance = _Instance(self.INSTANCE_NAME, client=client)
expire_timestamp = self._make_timestamp()
encryption_config = {
"encryption_type": CreateBackupEncryptionConfig.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION,
"kms_key_name": "key_name",
}
backup = self._make_one(
self.DST_BACKUP_ID,
instance,
source_backup=self.BACKUP_ID,
expire_time=expire_timestamp,
encryption_config=encryption_config,
)

with self.assertRaises(ValueError):
backup.create()

def test_copy_success(self):
from google.cloud.spanner_admin_database_v1 import CopyBackupRequest

op_future = object()
client = _Client()
api = client.database_admin_api = self._make_database_admin_api()
api.copy_backup.return_value = op_future

instance = _Instance(self.INSTANCE_NAME, client=client)
expire_timestamp = self._make_timestamp()
backup = self._make_one(
self.DST_BACKUP_ID,
instance,
source_backup=self.BACKUP_ID,
expire_time=expire_timestamp,
)

future = backup.create()
self.assertIs(future, op_future)

request = CopyBackupRequest(
parent=self.INSTANCE_NAME,
backup_id=self.DST_BACKUP_ID,
source_backup=self.BACKUP_ID,
expire_time=expire_timestamp,
)

api.copy_backup.assert_called_once_with(
request=request,
metadata=[("google-cloud-resource-prefix", backup.name)],
)

def test_copy_success_with_encryption_params(self):
from google.cloud.spanner_admin_database_v1 import CopyBackupRequest
from google.cloud.spanner_admin_database_v1 import CreateBackupEncryptionConfig

op_future = object()
client = _Client()
api = client.database_admin_api = self._make_database_admin_api()
api.copy_backup.return_value = op_future

instance = _Instance(self.INSTANCE_NAME, client=client)
expire_timestamp = self._make_timestamp()
encryption_config = {
"encryption_type": CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION,
"kms_key_name": "key_name",
}
backup = self._make_one(
self.DST_BACKUP_ID,
instance,
source_backup=self.BACKUP_ID,
expire_time=expire_timestamp,
encryption_config=encryption_config,
)

future = backup.create()
self.assertIs(future, op_future)

request = CopyBackupRequest(
parent=self.INSTANCE_NAME,
backup_id=self.DST_BACKUP_ID,
source_backup=self.BACKUP_ID,
expire_time=expire_timestamp,
encryption_config=encryption_config,
)

api.copy_backup.assert_called_once_with(
request=request,
metadata=[("google-cloud-resource-prefix", backup.name)],
)

def test_exists_grpc_error(self):
from google.api_core.exceptions import Unknown

Expand Down
87 changes: 87 additions & 0 deletions tests/unit/test_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class TestInstance(unittest.TestCase):
DATABASE_NAME = "%s/databases/%s" % (INSTANCE_NAME, DATABASE_ID)
LABELS = {"test": "true"}
FIELD_MASK = ["config", "display_name", "processing_units", "labels"]
BACKUP_ID = "backup_id"
DST_BACKUP_ID = "dst_backup_id"

def _getTargetClass(self):
from google.cloud.spanner_v1.instance import Instance
Expand Down Expand Up @@ -695,6 +697,91 @@ def test_backup_factory_explicit(self):
self.assertIs(backup._expire_time, timestamp)
self.assertEqual(backup._encryption_config, encryption_config)

def test_backup_factory_without_expiration_time(self):
from google.cloud.spanner_v1.backup import Backup

client = _Client(self.PROJECT)
instance = self._make_one(self.INSTANCE_ID, client, self.CONFIG_NAME)
BACKUP_ID = self.BACKUP_ID
DATABASE_NAME = self.DATABASE_NAME
backup = instance.backup(
BACKUP_ID,
database=DATABASE_NAME,
)

self.assertIsInstance(backup, Backup)
self.assertEqual(backup.backup_id, BACKUP_ID)
self.assertIs(backup._instance, instance)
self.assertEqual(backup._database, DATABASE_NAME)

def test_copy_backup(self):
import datetime
from google.cloud._helpers import UTC
from google.cloud.spanner_v1.backup import Backup

client = _Client(self.PROJECT)
instance = self._make_one(self.INSTANCE_ID, client, self.CONFIG_NAME)
BACKUP_ID = self.DST_BACKUP_ID
SOURCE_BACKUP = self.BACKUP_ID
timestamp = datetime.datetime.utcnow().replace(tzinfo=UTC)
backup = instance.copy_backup(
BACKUP_ID,
source_backup=SOURCE_BACKUP,
expire_time=timestamp,
)

self.assertIsInstance(backup, Backup)
self.assertEqual(backup.backup_id, self.DST_BACKUP_ID)
self.assertIs(backup._instance, instance)
self.assertEqual(backup._source_backup, self.BACKUP_ID)
self.assertEqual(backup.expire_time, timestamp)

def test_copy_backup_with_encryption_params(self):
import datetime
from google.cloud._helpers import UTC
from google.cloud.spanner_v1.backup import Backup
from google.cloud.spanner_admin_database_v1 import CreateBackupEncryptionConfig

client = _Client(self.PROJECT)
instance = self._make_one(self.INSTANCE_ID, client, self.CONFIG_NAME)
BACKUP_ID = self.DST_BACKUP_ID
SOURCE_BACKUP = self.BACKUP_ID
timestamp = datetime.datetime.utcnow().replace(tzinfo=UTC)
encryption_config = CreateBackupEncryptionConfig(
encryption_type=CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION,
kms_key_name="kms_key_name",
)
backup = instance.copy_backup(
BACKUP_ID,
source_backup=SOURCE_BACKUP,
expire_time=timestamp,
encryption_config=encryption_config,
)

self.assertIsInstance(backup, Backup)
self.assertEqual(backup.backup_id, self.DST_BACKUP_ID)
self.assertIs(backup._instance, instance)
self.assertEqual(backup._source_backup, self.BACKUP_ID)
self.assertEqual(backup.expire_time, timestamp)
self.assertEqual(backup._encryption_config, encryption_config)

def test_copy_backup_without_expiration_time(self):
from google.cloud.spanner_v1.backup import Backup

client = _Client(self.PROJECT)
instance = self._make_one(self.INSTANCE_ID, client, self.CONFIG_NAME)
BACKUP_ID = self.DST_BACKUP_ID
SOURCE_BACKUP = self.BACKUP_ID
backup = instance.copy_backup(
BACKUP_ID,
source_backup=SOURCE_BACKUP,
)

self.assertIsInstance(backup, Backup)
self.assertEqual(backup.backup_id, self.DST_BACKUP_ID)
self.assertIs(backup._instance, instance)
self.assertEqual(backup._source_backup, self.BACKUP_ID)

def test_list_backups_defaults(self):
from google.cloud.spanner_admin_database_v1 import Backup as BackupPB
from google.cloud.spanner_admin_database_v1 import DatabaseAdminClient
Expand Down