Skip to content
This repository has been archived by the owner on Aug 25, 2023. It is now read-only.

Update single table restore method #93

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9029460
YACHT-1094: Updated BackupListRestoreHandler to handle new parameters
przemyslaw-jasinski Nov 14, 2018
7f2f011
YACHT-1094: Updated BackupListRestoreService to handle new parameters
przemyslaw-jasinski Nov 15, 2018
543e6c7
YACHT-1094: BackupListRestore refactor
przemyslaw-jasinski Nov 15, 2018
6d90347
YACHT-1094: Fixed TestBackupListRestoreService
przemyslaw-jasinski Nov 15, 2018
724f3e9
YACHT-1094: Fixed TestBackupListRestoreHandler
przemyslaw-jasinski Nov 15, 2018
e0ccdd8
YACHT-1094: Added more test for validation in BackupListRestoreHandler
przemyslaw-jasinski Nov 15, 2018
6b8728b
YACHT-1094: BackupListRestoreHandler small refactor
przemyslaw-jasinski Nov 15, 2018
b745d69
YACHT-1094: Fixed TestTableRestoreService
przemyslaw-jasinski Nov 15, 2018
ebf791c
Merge branch 'YACHT-1094' into YACHT-1093
radkomateusz Nov 19, 2018
246f984
YACHT-1093: Restored data doesn't expire after restoration. Passing c…
radkomateusz Nov 19, 2018
877dc9a
YACHT-1093: Common parameters validations for restore handlers
radkomateusz Nov 19, 2018
c57d87d
YACHT-1093: Adding default_restoration_project_id parameter in config…
radkomateusz Nov 19, 2018
88d52a4
YACHT-1093: Restore handlers refactor
przemyslaw-jasinski Nov 19, 2018
4cffdf7
YACHT-1093: Fixing unit tests
radkomateusz Nov 19, 2018
d467435
YACHT-1093: Fix dataset restore UI - params from target project and d…
radkomateusz Nov 19, 2018
8d43df8
YACHT-1093: Fix dataset restore UI - params from target project and d…
radkomateusz Nov 19, 2018
2b053f1
YACHT-1093: Updated restoreList UI
przemyslaw-jasinski Nov 19, 2018
121f1a9
YACHT-1094: restoreList html restyled
przemyslaw-jasinski Nov 20, 2018
0e9c637
YACHT-1093: Adding unit tests for handler. Improving validator to sup…
radkomateusz Nov 20, 2018
00425be
YACHT-1094: Fixed restoreList descriptions
przemyslaw-jasinski Nov 20, 2018
20819fa
YACHT-1093: Adding validation checking if target project id is not None.
radkomateusz Nov 20, 2018
07d6c32
YACHT-1093: Removing restoration_project_id from configuration. List …
radkomateusz Nov 20, 2018
13520cf
YACHT-1093: Removing restoration_project_id from configuration. List …
radkomateusz Nov 20, 2018
a32acf3
YACHT-1094: Updated restoreList.html
przemyslaw-jasinski Nov 20, 2018
ac352ff
YACHT-1093: Repair unit tests
radkomateusz Nov 20, 2018
12d638c
YACHT-1094: Moved validators to commons.handlers package
przemyslaw-jasinski Nov 20, 2018
42f83d4
YACHT-1094: Added tests for validators class
przemyslaw-jasinski Nov 20, 2018
c026246
YACHT-1099: Updated TableRestore to handle more parameters
przemyslaw-jasinski Nov 20, 2018
8200d6e
YACHT-1099: Updated test for TestTableRestoreService
przemyslaw-jasinski Nov 20, 2018
8ab0fbe
YACHT-1099: Updated test for TestTableRestoreHandler
przemyslaw-jasinski Nov 20, 2018
5133476
YACHT-1099: Updated restoreTable.html
przemyslaw-jasinski Nov 20, 2018
8ca86b2
YACHT-1099: Added default values for write and create disposition in …
przemyslaw-jasinski Nov 21, 2018
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
4 changes: 2 additions & 2 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ project_settings:
# (it usually is the same project on which BBQ runs)
backup_project_id: 'BBQ-project-id'

# restoration_project_id - project into which data will be restored during restoration process
restoration_project_id: 'restoration-storage-project-id'
# default_restoration_project_id - project into which data will be restored by default during restoration process
default_restoration_project_id: 'default-restoration-storage-project-id'
debug_mode: False

tests:
Expand Down
16 changes: 0 additions & 16 deletions src/commons/big_query/validators.py

This file was deleted.

4 changes: 2 additions & 2 deletions src/commons/config/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def backup_project_id(self):
return self.__project_config['project_settings'].get('backup_project_id')

@property
def restoration_project_id(self):
return self.__project_config['project_settings'].get('restoration_project_id')
def default_restoration_project_id(self):
return self.__project_config['project_settings'].get('default_restoration_project_id')

@property
def projects_to_skip(self):
Expand Down
84 changes: 84 additions & 0 deletions src/commons/handlers/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import re

from src.commons.exceptions import ParameterValidationException


class WrongDatasetNameException(Exception):
pass


class WrongProjectNameException(Exception):
pass


class WrongWriteDispositionException(Exception):
pass


class WrongCreateDispositionException(Exception):
pass


project_id_pattern = re.compile("^[a-zA-Z0-9-]+$")
dataset_id_pattern = re.compile("^[a-zA-Z0-9_]+$")
available_create_dispositions = ["CREATE_IF_NEEDED", "CREATE_NEVER"]
available_write_dispositions = ["WRITE_TRUNCATE", "WRITE_APPEND", "WRITE_EMPTY"]


def validate_restore_request_params(
source_project_id=None, source_dataset_id=None,
target_project_id=None, target_dataset_id=None,
create_disposition=None, write_disposition=None):
try:
if source_project_id:
validate_project_id(source_project_id)
if source_dataset_id:
validate_dataset_id(source_dataset_id)
if target_project_id:
validate_project_id(target_project_id)
if target_dataset_id:
validate_dataset_id(target_dataset_id)
if write_disposition:
validate_write_disposition(write_disposition)
if create_disposition:
validate_create_disposition(create_disposition)

except (WrongDatasetNameException,
WrongProjectNameException,
WrongWriteDispositionException,
WrongCreateDispositionException), e:

raise ParameterValidationException(e.message)


def validate_project_id(project_id):
if not project_id or not project_id_pattern.match(project_id):
error_message = "Invalid project value: '{}'. Project IDs may " \
"contain letters, numbers, and " \
"dash".format(project_id)
raise WrongProjectNameException(error_message)


def validate_dataset_id(dataset_id):
if not dataset_id or not dataset_id_pattern.match(dataset_id):
error_message = "Invalid dataset value: '{}'. Dataset IDs may " \
"contain letters, numbers, and " \
"underscores".format(dataset_id)
raise WrongDatasetNameException(error_message)


def validate_write_disposition(write_disposition):
if write_disposition not in available_write_dispositions:
error_message = "Invalid write disposition: '{}'. " \
"The following values are supported: {}." \
.format(write_disposition, ', '.join(available_write_dispositions))
raise WrongWriteDispositionException(error_message)


def validate_create_disposition(create_disposition):
if create_disposition not in available_create_dispositions:
error_message = "Invalid create disposition: '{}'. " \
"The following values are supported: {}." \
.format(create_disposition,
', '.join(available_create_dispositions))
raise WrongCreateDispositionException(error_message)
50 changes: 26 additions & 24 deletions src/restore/dataset/dataset_restore_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@

import webapp2

from src.commons.exceptions import ParameterValidationException
from src.commons.handlers.json_handler import JsonHandler
from src.commons.handlers.bbq_authenticated_handler import BbqAuthenticatedHandler
from src.commons.big_query import validators
from src.commons.big_query.validators import WrongDatasetNameException
from src.commons.handlers import validators
from src.commons.config.configuration import configuration
from src.commons.handlers.bbq_authenticated_handler import \
BbqAuthenticatedHandler
from src.commons.handlers.json_handler import JsonHandler
from src.restore.dataset.dataset_restore_service import \
DatasetRestoreService
from src.restore.status.restoration_job_status_service import \
Expand All @@ -18,11 +17,20 @@
class DatasetRestoreHandler(JsonHandler):

def post(self, project_id, dataset_id):

target_project_id = self.request.get('targetProjectId', None)
target_dataset_id = self.request.get('targetDatasetId', None)
create_disposition = self.request.get('createDisposition', None)
write_disposition = self.request.get('writeDisposition', None)
max_partition_days = self.__get_max_partition_days()

self.__validate_params(dataset_id, target_dataset_id)
validators.validate_restore_request_params(
source_project_id=project_id,
source_dataset_id=dataset_id,
target_project_id=target_project_id,
target_dataset_id=target_dataset_id,
create_disposition=create_disposition,
write_disposition=write_disposition
)

restoration_job_id = str(uuid.uuid4())
logging.info("Created restoration_job_id: %s", restoration_job_id)
Expand All @@ -31,34 +39,26 @@ def post(self, project_id, dataset_id):
restoration_job_id=restoration_job_id,
project_id=project_id,
dataset_id=dataset_id,
target_project_id=target_project_id,
target_dataset_id=target_dataset_id,
create_disposition=create_disposition,
write_disposition=write_disposition,
max_partition_days=max_partition_days
)

restore_data = {
'restorationJobId': restoration_job_id,
'projectId': project_id,
'datasetId': dataset_id,
'restorationStatusEndpoint': RestorationJobStatusService.get_status_endpoint(
restoration_job_id),
'restorationWarningsOnlyStatusEndpoint': RestorationJobStatusService.get_warnings_only_status_endpoint(
restoration_job_id)
'restorationStatusEndpoint': RestorationJobStatusService.get_status_endpoint(restoration_job_id),
'restorationWarningsOnlyStatusEndpoint': RestorationJobStatusService.get_warnings_only_status_endpoint(restoration_job_id)
}
self._finish_with_success(restore_data)

def __get_max_partition_days(self):
max_partition_days = self.request.get('maxPartitionDays', None)
return int(max_partition_days) if max_partition_days else None

@staticmethod
def __validate_params(dataset_id, target_dataset_id):
try:
validators.validate_dataset_id(dataset_id)
if target_dataset_id:
validators.validate_dataset_id(target_dataset_id)
except WrongDatasetNameException, e:
raise ParameterValidationException(e.message)


class DatasetRestoreAuthenticatedHandler(DatasetRestoreHandler,
BbqAuthenticatedHandler):
Expand All @@ -69,8 +69,10 @@ def __init__(self, request=None, response=None):


app = webapp2.WSGIApplication([
webapp2.Route('/restore/project/<project_id:.*>/dataset/<dataset_id:.*>',
DatasetRestoreHandler),
webapp2.Route('/schedule/restore/project/<project_id:.*>/dataset/<dataset_id:.*>',
DatasetRestoreAuthenticatedHandler)
webapp2.Route(
'/restore/project/<project_id:.*>/dataset/<dataset_id:.*>',
DatasetRestoreHandler),
webapp2.Route(
'/schedule/restore/project/<project_id:.*>/dataset/<dataset_id:.*>',
DatasetRestoreAuthenticatedHandler)
], debug=configuration.debug_mode)
22 changes: 13 additions & 9 deletions src/restore/dataset/dataset_restore_items_generator.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import logging

from src.backup.datastore.Table import Table
from src.commons.collections import paginated
from src.commons.decorators.log_time import log_time
from src.backup.datastore.Table import Table
from src.commons.table_reference import TableReference
from src.restore.datastore.restore_item import RestoreItem
from src.restore.restoration_table_reference import RestoreTableReference

Expand All @@ -11,8 +12,8 @@ class DatasetRestoreItemsGenerator(object):

@classmethod
@log_time
def generate_restore_items(cls, project_id, dataset_id, target_dataset_id,
max_partition_days):
def generate_restore_items(cls, project_id, dataset_id, target_project_id,
target_dataset_id, max_partition_days):
if max_partition_days:
table_entities = Table \
.get_tables_with_max_partition_days(project_id, dataset_id,
Expand All @@ -29,13 +30,16 @@ def generate_restore_items(cls, project_id, dataset_id, target_dataset_id,
RestoreTableReference.backup_table_reference(
table_entity, backup_entity)

target_table_reference = \
RestoreTableReference.target_table_reference(
table_entity, target_dataset_id)
target_table_reference = TableReference(
target_project_id,
target_dataset_id,
table_entity.table_id,
table_entity.partition_id
)

restore_items.append(
RestoreItem.create(source_table_reference,
target_table_reference))
restore_item = RestoreItem.create(source_table_reference,
target_table_reference)
restore_items.append(restore_item)

logging.info("Restore items generator yields %s restore items",
len(restore_items))
Expand Down
21 changes: 12 additions & 9 deletions src/restore/dataset/dataset_restore_parameters_validator.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import logging

from src.commons.exceptions import ParameterValidationException
from src.backup.datastore.Table import Table
from src.commons.big_query.big_query import BigQuery
from src.commons.config.configuration import configuration
from src.commons.exceptions import ParameterValidationException


class DatasetRestoreParametersValidator(object):

def __init__(self):
self.BQ = BigQuery()

def validate_parameters(self, project_id, dataset_id, target_dataset_id,
max_partition_days):
def validate_parameters(self, project_id, dataset_id, target_project_id,
target_dataset_id, max_partition_days):

if target_project_id is None:
raise ParameterValidationException("Required target project id parameter is None")

any_backup = self.__get_backup(project_id, dataset_id,
max_partition_days)

self.__validate_locations(any_backup, target_dataset_id)
self.__validate_locations(any_backup, target_project_id, target_dataset_id)

def __get_backup(self, project_id, dataset_id, max_partition_days):
logging.info(
Expand Down Expand Up @@ -53,10 +56,10 @@ def __get_tables(self, project_id, dataset_id, max_partition_days):
max_partition_days,
page_size=20)

def __get_target_dataset_location(self, target_dataset_id):
def __get_target_dataset_location(self, target_project_id, target_dataset_id):
target_dataset = self.BQ.get_dataset(
configuration.restoration_project_id,
target_dataset_id)
project_id=target_project_id,
dataset_id=target_dataset_id)
if target_dataset is None:
return None
return self.__extract_location(target_dataset)
Expand All @@ -68,8 +71,8 @@ def __get_backup_dataset_location(self, any_backup):
return None
return self.__extract_location(backup_dataset)

def __validate_locations(self, any_backup, target_dataset_id):
target_location = self.__get_target_dataset_location(target_dataset_id)
def __validate_locations(self, any_backup, target_project_id, target_dataset_id):
target_location = self.__get_target_dataset_location(target_project_id,target_dataset_id)

if target_location is None:
return
Expand Down
Loading