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
changes for cross region backup
  • Loading branch information
asthamohta committed Mar 10, 2022
commit 9d6f8ecd515f4ee704a1855dcb18a5503024629e
2 changes: 0 additions & 2 deletions google/cloud/spanner_v1/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ def encryption_info(self):
@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
Expand All @@ -211,7 +210,6 @@ def max_expire_time(self):
@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
Expand Down
9 changes: 2 additions & 7 deletions google/cloud/spanner_v1/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,26 +555,21 @@ def copy_backup(
self, backup_id, source_backup, expire_time=None, encryption_config=None,
):
"""Factory to create a copy backup within this instance.

:type backup_id: str
:param backup_id: The ID of the backup copy.

:type source_backup_id: str
:param backup_id: The ID of the source backup to be copied.

:type source_backup: str
:param source_backup_id: The full path of the source backup to be copied.
: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.

:type encryption_config:
:class:`~google.cloud.spanner_admin_database_v1.types.CopyBackupEncryptionConfig`
or :class:`dict`
:param encryption_config:
(Optional) Encryption configuration for the backup.
If a dict is provided, it must be of the same form as the protobuf
message :class:`~google.cloud.spanner_admin_database_v1.types.CopyBackupEncryptionConfig`

:rtype: :class:`~google.cloud.spanner_v1.backup.Backup`
:returns: a copy backup owned by this instance.
"""
Expand Down
33 changes: 32 additions & 1 deletion samples/samples/backup_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,8 @@ def update_backup(instance_id, backup_id):

# Expire time must be within 366 days of the create time of the backup.
old_expire_time = backup.expire_time
new_expire_time = old_expire_time + timedelta(days=30)
# New expire time should be less than the max expire time
new_expire_time = min(backup.max_expire_time, old_expire_time + timedelta(days=30))
backup.update_expire_time(new_expire_time)
print(
"Backup {} expire time was updated from {} to {}.".format(
Expand Down Expand Up @@ -381,6 +382,33 @@ def create_database_with_version_retention_period(instance_id, database_id, rete
# [END spanner_create_database_with_version_retention_period]


# [START spanner_copy_backup]
def copy_backup(instance_id, backup_id, source_backup_path):
"""Copies a backup."""
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)

# Create a backup object and wait for copy backup operation to complete.
expire_time = datetime.utcnow() + timedelta(days=14)
copy_backup = instance.copy_backup(backup_id, source_backup=source_backup_path, expire_time=expire_time)
operation = copy_backup.create()

# Wait for copy backup operation to complete.
operation.result(2100)

# Verify that the copy backup is ready.
copy_backup.reload()
assert copy_backup.is_ready() is True

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]


if __name__ == "__main__": # noqa: C901
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
Expand All @@ -404,6 +432,7 @@ def create_database_with_version_retention_period(instance_id, database_id, rete
"list_database_operations", help=list_database_operations.__doc__
)
subparsers.add_parser("delete_backup", help=delete_backup.__doc__)
subparsers.add_parser("copy_backup", help=copy_backup.__doc__)

args = parser.parse_args()

Expand All @@ -423,5 +452,7 @@ def create_database_with_version_retention_period(instance_id, database_id, rete
list_database_operations(args.instance_id)
elif args.command == "delete_backup":
delete_backup(args.instance_id, args.backup_id)
elif args.command == "copy_backup":
copy_backup(args.instance_id, args.backup_id, args.source_backup_id)
else:
print("Command {} did not match expected commands.".format(args.command))
18 changes: 17 additions & 1 deletion samples/samples/backup_sample_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def unique_backup_id():
CMEK_BACKUP_ID = unique_backup_id()
RETENTION_DATABASE_ID = unique_database_id()
RETENTION_PERIOD = "7d"
COPY_BACKUP_ID = unique_backup_id()


@pytest.mark.dependency(name="create_backup")
Expand Down Expand Up @@ -125,11 +126,14 @@ def test_update_backup(capsys, instance_id):
assert BACKUP_ID in out


@pytest.mark.dependency(depends=["create_backup"])
@pytest.mark.dependency(depends=["create_backup","copy_backup"])
def test_delete_backup(capsys, instance_id):
backup_sample.delete_backup(instance_id, BACKUP_ID)
out, _ = capsys.readouterr()
assert BACKUP_ID in out
backup_sample.delete_backup(instance_id, COPY_BACKUP_ID)
out, _ = capsys.readouterr()
assert COPY_BACKUP_ID in out


@pytest.mark.dependency(depends=["create_backup"])
Expand All @@ -155,3 +159,15 @@ def test_create_database_with_retention_period(capsys, sample_instance):
assert ("retention period " + RETENTION_PERIOD) in out
database = sample_instance.database(RETENTION_DATABASE_ID)
database.drop()

@pytest.mark.dependency(name="copy_backup",depends=["create_backup"])
def test_copy_backup(capsys, instance_id, spanner_client):
source_backp_path=spanner_client.project_name+'/instances/'+instance_id+'/backups/'+BACKUP_ID
backup_sample.copy_backup(
instance_id,
source_backp_path,
BACKUP_ID
)
out, _ = capsys.readouterr()
assert COPY_BACKUP_ID in out

3 changes: 3 additions & 0 deletions tests/system/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

from google.cloud import spanner_v1
from . import _helpers
from google.cloud.spanner_admin_database_v1.types.backup import (
CreateBackupEncryptionConfig,
)


@pytest.fixture(scope="function")
Expand Down