-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: implement backup support * Apply suggestions from code review Co-Authored-By: skuruppu <[email protected]> * refactor restore to use source Co-authored-by: larkee <[email protected]> Co-authored-by: skuruppu <[email protected]>
- Loading branch information
1 parent
a48c4b1
commit 39288e7
Showing
6 changed files
with
1,725 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,275 @@ | ||
# Copyright 2020 Google LLC All rights reserved. | ||
# | ||
# 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. | ||
|
||
"""User friendly container for Cloud Spanner Backup.""" | ||
|
||
import re | ||
|
||
from google.cloud._helpers import _datetime_to_pb_timestamp, _pb_timestamp_to_datetime | ||
from google.cloud.exceptions import NotFound | ||
|
||
from google.cloud.spanner_admin_database_v1.gapic import enums | ||
from google.cloud.spanner_v1._helpers import _metadata_with_prefix | ||
|
||
_BACKUP_NAME_RE = re.compile( | ||
r"^projects/(?P<project>[^/]+)/" | ||
r"instances/(?P<instance_id>[a-z][-a-z0-9]*)/" | ||
r"backups/(?P<backup_id>[a-z][a-z0-9_\-]*[a-z0-9])$" | ||
) | ||
|
||
|
||
class Backup(object): | ||
"""Representation of a Cloud Spanner Backup. | ||
We can use a :class`Backup` to: | ||
* :meth:`create` the backup | ||
* :meth:`update` the backup | ||
* :meth:`delete` the backup | ||
:type backup_id: str | ||
:param backup_id: The ID of the backup. | ||
:type instance: :class:`~google.cloud.spanner_v1.instance.Instance` | ||
:param instance: The instance that owns the backup. | ||
:type database: str | ||
:param database: (Optional) The URI of the database that the backup is | ||
for. Required if the create method needs to be called. | ||
: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. | ||
""" | ||
|
||
def __init__(self, backup_id, instance, database="", expire_time=None): | ||
self.backup_id = backup_id | ||
self._instance = instance | ||
self._database = database | ||
self._expire_time = expire_time | ||
self._create_time = None | ||
self._size_bytes = None | ||
self._state = None | ||
self._referencing_databases = None | ||
|
||
@property | ||
def name(self): | ||
"""Backup name used in requests. | ||
The backup name is of the form | ||
``"projects/../instances/../backups/{backup_id}"`` | ||
:rtype: str | ||
:returns: The backup name. | ||
""" | ||
return self._instance.name + "/backups/" + self.backup_id | ||
|
||
@property | ||
def database(self): | ||
"""Database name used in requests. | ||
The database name is of the form | ||
``"projects/../instances/../backups/{backup_id}"`` | ||
:rtype: str | ||
:returns: The database name. | ||
""" | ||
return self._database | ||
|
||
@property | ||
def expire_time(self): | ||
"""Expire time used in creation requests. | ||
:rtype: :class:`datetime.datetime` | ||
:returns: a datetime object representing the expire time of | ||
this backup | ||
""" | ||
return self._expire_time | ||
|
||
@property | ||
def create_time(self): | ||
"""Create time of this backup. | ||
:rtype: :class:`datetime.datetime` | ||
:returns: a datetime object representing the create time of | ||
this backup | ||
""" | ||
return self._create_time | ||
|
||
@property | ||
def size_bytes(self): | ||
"""Size of this backup in bytes. | ||
:rtype: int | ||
:returns: the number size of this backup measured in bytes | ||
""" | ||
return self._size_bytes | ||
|
||
@property | ||
def state(self): | ||
"""State of this backup. | ||
:rtype: :class:`~google.cloud.spanner_admin_database_v1.gapic.enums.Backup.State` | ||
:returns: an enum describing the state of the backup | ||
""" | ||
return self._state | ||
|
||
@property | ||
def referencing_databases(self): | ||
"""List of databases referencing this backup. | ||
:rtype: list of strings | ||
:returns: a list of database path strings which specify the databases still | ||
referencing this backup | ||
""" | ||
return self._referencing_databases | ||
|
||
@classmethod | ||
def from_pb(cls, backup_pb, instance): | ||
"""Create an instance of this class from a protobuf message. | ||
:type backup_pb: :class:`~google.spanner.admin.database.v1.Backup` | ||
:param backup_pb: A backup protobuf object. | ||
:type instance: :class:`~google.cloud.spanner_v1.instance.Instance` | ||
:param instance: The instance that owns the backup. | ||
:rtype: :class:`Backup` | ||
:returns: The backup parsed from the protobuf response. | ||
:raises ValueError: | ||
if the backup name does not match the expected format or if | ||
the parsed project ID does not match the project ID on the | ||
instance's client, or if the parsed instance ID does not match | ||
the instance's ID. | ||
""" | ||
match = _BACKUP_NAME_RE.match(backup_pb.name) | ||
if match is None: | ||
raise ValueError( | ||
"Backup protobuf name was not in the expected format.", backup_pb.name | ||
) | ||
if match.group("project") != instance._client.project: | ||
raise ValueError( | ||
"Project ID on backup does not match the project ID" | ||
"on the instance's client" | ||
) | ||
instance_id = match.group("instance_id") | ||
if instance_id != instance.instance_id: | ||
raise ValueError( | ||
"Instance ID on database does not match the instance ID" | ||
"on the instance" | ||
) | ||
backup_id = match.group("backup_id") | ||
return cls(backup_id, instance) | ||
|
||
def create(self): | ||
"""Create this backup within its instance. | ||
:rtype: :class:`~google.api_core.operation.Operation` | ||
:returns: a future used to poll the status of the create request | ||
:raises Conflict: if the backup already exists | ||
:raises NotFound: if the instance owning the backup does not exist | ||
: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: | ||
raise ValueError("database not set") | ||
api = self._instance._client.database_admin_api | ||
metadata = _metadata_with_prefix(self.name) | ||
backup = { | ||
"database": self._database, | ||
"expire_time": _datetime_to_pb_timestamp(self.expire_time), | ||
} | ||
|
||
future = api.create_backup( | ||
self._instance.name, self.backup_id, backup, metadata=metadata | ||
) | ||
return future | ||
|
||
def exists(self): | ||
"""Test whether this backup exists. | ||
:rtype: bool | ||
:returns: True if the backup exists, else False. | ||
""" | ||
api = self._instance._client.database_admin_api | ||
metadata = _metadata_with_prefix(self.name) | ||
|
||
try: | ||
api.get_backup(self.name, metadata=metadata) | ||
except NotFound: | ||
return False | ||
return True | ||
|
||
def reload(self): | ||
"""Reload this backup. | ||
Refresh the stored backup properties. | ||
:raises NotFound: if the backup does not exist | ||
""" | ||
api = self._instance._client.database_admin_api | ||
metadata = _metadata_with_prefix(self.name) | ||
pb = api.get_backup(self.name, metadata=metadata) | ||
self._database = pb.database | ||
self._expire_time = _pb_timestamp_to_datetime(pb.expire_time) | ||
self._create_time = _pb_timestamp_to_datetime(pb.create_time) | ||
self._size_bytes = pb.size_bytes | ||
self._state = enums.Backup.State(pb.state) | ||
self._referencing_databases = pb.referencing_databases | ||
|
||
def update_expire_time(self, new_expire_time): | ||
"""Update the expire time of this backup. | ||
:type new_expire_time: :class:`datetime.datetime` | ||
:param new_expire_time: the new expire time timestamp | ||
""" | ||
api = self._instance._client.database_admin_api | ||
metadata = _metadata_with_prefix(self.name) | ||
backup_update = { | ||
"name": self.name, | ||
"expire_time": _datetime_to_pb_timestamp(new_expire_time), | ||
} | ||
update_mask = {"paths": ["expire_time"]} | ||
api.update_backup(backup_update, update_mask, metadata=metadata) | ||
self._expire_time = new_expire_time | ||
|
||
def is_ready(self): | ||
"""Test whether this backup is ready for use. | ||
:rtype: bool | ||
:returns: True if the backup state is READY, else False. | ||
""" | ||
return self.state == enums.Backup.State.READY | ||
|
||
def delete(self): | ||
"""Delete this backup.""" | ||
api = self._instance._client.database_admin_api | ||
metadata = _metadata_with_prefix(self.name) | ||
api.delete_backup(self.name, metadata=metadata) | ||
|
||
|
||
class BackupInfo(object): | ||
def __init__(self, backup, create_time, source_database): | ||
self.backup = backup | ||
self.create_time = _pb_timestamp_to_datetime(create_time) | ||
self.source_database = source_database | ||
|
||
@classmethod | ||
def from_pb(cls, pb): | ||
return cls(pb.backup, pb.create_time, pb.source_database) |
Oops, something went wrong.