Skip to content

feat: add support for spanner copy backup feature #600

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

Merged
merged 13 commits into from
Mar 25, 2022
Prev Previous commit
Next Next commit
feat: changes as per review, adding shared_backup
  • Loading branch information
asthamohta committed Dec 24, 2021
commit cf138b7aa9e508afd7ed40d527029848f22fc619
26 changes: 25 additions & 1 deletion google/cloud/spanner_v1/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ def __init__(
backup_id,
instance,
database="",
source_backup=None,
expire_time=None,
version_time=None,
encryption_config=None,
source_backup=None,
):
self.backup_id = backup_id
self._instance = instance
Expand All @@ -92,6 +92,8 @@ def __init__(
self._state = None
self._referencing_databases = None
self._encryption_info = None
self._max_expire_time = None
self._referencing_backups = None
if type(encryption_config) == dict:
if source_backup:
self._encryption_config = CopyBackupEncryptionConfig(
Expand Down Expand Up @@ -196,6 +198,26 @@ def encryption_info(self):
"""
return self._encryption_info

@property
def max_expire_time(self):
"""The max allowed expiration time of the backup.

:rtype: :class:`datetime.datetime`
:returns: a datetime object representing the max expire time of
this backup
"""
return self._max_expire_time

@property
def referencing_backups(self):
"""The names of the destination backups being created by copying this source backup.

:rtype: list of strings
:returns: a list of backup path strings which specify the backups that are
referencing this copy backup
"""
return self._referencing_backups

@classmethod
def from_pb(cls, backup_pb, instance):
"""Create an instance of this class from a protobuf message.
Expand Down Expand Up @@ -330,6 +352,8 @@ def reload(self):
self._state = BackupPB.State(pb.state)
self._referencing_databases = pb.referencing_databases
self._encryption_info = pb.encryption_info
self._max_expire_time = pb.max_expire_time
self._referencing_backups = pb.referencing_backups

def update_expire_time(self, new_expire_time):
"""Update the expire time of this backup.
Expand Down
3 changes: 3 additions & 0 deletions tests/system/_helpers.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
DATABASE_OPERATION_TIMEOUT_IN_SECONDS = int(
os.getenv("SPANNER_DATABASE_OPERATION_TIMEOUT_IN_SECONDS", 60)
)
BACKUP_OPERATION_TIMEOUT_IN_SECONDS = int(
os.getenv("SPANNER_BACKUP_OPERATION_TIMEOUT_IN_SECONDS", 1200)
)

USE_EMULATOR_ENVVAR = "SPANNER_EMULATOR_HOST"
USE_EMULATOR = os.getenv(USE_EMULATOR_ENVVAR) is not None
Expand Down
33 changes: 33 additions & 0 deletions tests/system/conftest.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime
import time
from google.cloud.spanner_admin_database_v1.types.backup import (
CreateBackupEncryptionConfig,
)

import pytest

Expand Down Expand Up @@ -67,6 +71,11 @@ def database_operation_timeout():
return _helpers.DATABASE_OPERATION_TIMEOUT_IN_SECONDS


@pytest.fixture(scope="session")
def backup_operation_timeout():
return _helpers.BACKUP_OPERATION_TIMEOUT_IN_SECONDS


@pytest.fixture(scope="session")
def shared_instance_id():
if _helpers.CREATE_INSTANCE:
Expand Down Expand Up @@ -148,6 +157,30 @@ def shared_database(shared_instance, database_operation_timeout):
database.drop()


@pytest.fixture(scope="session")
def shared_backup(shared_instance, shared_database, backup_operation_timeout):
backup_name = _helpers.unique_id("test_backup")
expire_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
days=3
)
source_encryption_enum = CreateBackupEncryptionConfig.EncryptionType
source_encryption_config = CreateBackupEncryptionConfig(
encryption_type=source_encryption_enum.GOOGLE_DEFAULT_ENCRYPTION,
)
backup = shared_instance.backup(
backup_name,
database=shared_database,
expire_time=expire_time,
encryption_config=source_encryption_config,
)
operation = backup.create()
operation.result(backup_operation_timeout) # raises on failure / timeout.

yield backup

backup.delete()


@pytest.fixture(scope="function")
def databases_to_delete():
to_delete = []
Expand Down
40 changes: 6 additions & 34 deletions tests/system/test_backup_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def test_backup_workflow(


def test_copy_backup_workflow(
shared_instance, shared_database, database_version_time, backups_to_delete,
shared_instance, shared_backup, backups_to_delete,
):
from google.cloud.spanner_admin_database_v1 import (
CreateBackupEncryptionConfig,
Expand All @@ -209,7 +209,6 @@ def test_copy_backup_workflow(
)

backup_id = _helpers.unique_id("backup_id", separator="_")
source_backup_id = _helpers.unique_id("source_backup_id", separator="_")
expire_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
days=3
)
Expand All @@ -218,27 +217,12 @@ def test_copy_backup_workflow(
encryption_type=copy_encryption_enum.GOOGLE_DEFAULT_ENCRYPTION,
)

source_encryption_enum = CreateBackupEncryptionConfig.EncryptionType
source_encryption_config = CreateBackupEncryptionConfig(
encryption_type=source_encryption_enum.GOOGLE_DEFAULT_ENCRYPTION,
)

# Create backup.
source_backup = shared_instance.backup(
source_backup_id,
database=shared_database,
expire_time=expire_time,
version_time=database_version_time,
encryption_config=source_encryption_config,
)
operation = source_backup.create()
backups_to_delete.append(source_backup)
operation.result() # blocks indefinitely

shared_backup.reload()
# Create a copy backup
copy_backup = shared_instance.copy_backup(
backup_id=backup_id,
source_backup=source_backup.name,
source_backup=shared_backup.name,
expire_time=expire_time,
encryption_config=copy_encryption_config,
)
Expand Down Expand Up @@ -268,7 +252,6 @@ def test_copy_backup_workflow(
copy_backup.update_expire_time(valid_expire_time)
assert valid_expire_time == copy_backup.expire_time

source_backup.delete()
copy_backup.delete()
assert not copy_backup.exists()

Expand Down Expand Up @@ -364,34 +347,23 @@ def test_backup_create_w_invalid_version_time_future(


def test_copy_backup_create_w_invalid_expire_time(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer to include this case in test_copy_backup_workflow in order to reuse the created source backup and minimize the number of created backups.

shared_instance, shared_database, backups_to_delete,
shared_instance, shared_backup,
):
backup_id = _helpers.unique_id("backup_id", separator="_")
source_backup_id = _helpers.unique_id("source_backup_id", separator="_")
valid_expire_time = datetime.datetime.now(
datetime.timezone.utc
) + datetime.timedelta(days=7)
invalid_expire_time = datetime.datetime.now(datetime.timezone.utc)

source_backup = shared_instance.backup(
source_backup_id, database=shared_database, expire_time=valid_expire_time
)
op = source_backup.create()
op.result() # blocks indefinitely
backups_to_delete.append(source_backup)
shared_backup.reload()

copy_backup = shared_instance.copy_backup(
backup_id=backup_id,
source_backup=source_backup.name,
source_backup=shared_backup.name,
expire_time=invalid_expire_time,
)

with pytest.raises(exceptions.InvalidArgument):
operation = copy_backup.create()
operation.result() # blocks indefinitely

source_backup.delete()


def test_database_restore_to_diff_instance(
shared_instance,
Expand Down