goe-framework 1.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- goe/__init__.py +17 -0
- goe/conductor/__init__.py +0 -0
- goe/conductor/hybrid_view_service.py +334 -0
- goe/config/__init__.py +0 -0
- goe/config/config_file.py +53 -0
- goe/config/config_validation_functions.py +663 -0
- goe/config/option_descriptions.py +38 -0
- goe/config/orchestration_config.py +996 -0
- goe/config/orchestration_defaults.py +999 -0
- goe/connect/__init__.py +0 -0
- goe/connect/connect.py +555 -0
- goe/connect/connect_backend.py +481 -0
- goe/connect/connect_constants.py +23 -0
- goe/connect/connect_frontend.py +265 -0
- goe/connect/connect_functions.py +112 -0
- goe/connect/connect_transport.py +336 -0
- goe/data_governance/__init__.py +0 -0
- goe/data_governance/cloudera_navigator.py +845 -0
- goe/data_governance/hadoop_data_governance.py +616 -0
- goe/data_governance/hadoop_data_governance_constants.py +90 -0
- goe/data_governance/hadoop_data_governance_interface.py +209 -0
- goe/exceptions.py +25 -0
- goe/filesystem/__init__.py +0 -0
- goe/filesystem/cli_hdfs.py +539 -0
- goe/filesystem/goe_azure.py +483 -0
- goe/filesystem/goe_dfs.py +424 -0
- goe/filesystem/goe_dfs_factory.py +110 -0
- goe/filesystem/goe_gcs.py +352 -0
- goe/filesystem/goe_s3.py +363 -0
- goe/filesystem/web_hdfs.py +507 -0
- goe/goe.py +3526 -0
- goe/listener/__init__.py +0 -0
- goe/listener/__main__.py +36 -0
- goe/listener/api/__init__.py +0 -0
- goe/listener/api/routes/__init__.py +0 -0
- goe/listener/api/routes/docs.py +68 -0
- goe/listener/api/routes/orchestration.py +185 -0
- goe/listener/api/routes/system.py +186 -0
- goe/listener/asgi.py +120 -0
- goe/listener/config/__init__.py +10 -0
- goe/listener/config/application.py +113 -0
- goe/listener/config/gunicorn.conf.py +284 -0
- goe/listener/config/logging.py +81 -0
- goe/listener/config/router.py +44 -0
- goe/listener/core/__init__.py +0 -0
- goe/listener/core/enum.py +24 -0
- goe/listener/core/events.py +44 -0
- goe/listener/core/middleware/__init__.py +6 -0
- goe/listener/core/middleware/compression.py +297 -0
- goe/listener/core/middleware/cors.py +38 -0
- goe/listener/core/middleware/secure_headers.py +37 -0
- goe/listener/core/security.py +46 -0
- goe/listener/core/worker.py +149 -0
- goe/listener/exceptions/__init__.py +38 -0
- goe/listener/exceptions/base.py +279 -0
- goe/listener/exceptions/errors.py +99 -0
- goe/listener/heartbeat.py +47 -0
- goe/listener/prestart.py +85 -0
- goe/listener/schemas/__init__.py +58 -0
- goe/listener/schemas/base.py +102 -0
- goe/listener/schemas/error.py +133 -0
- goe/listener/schemas/generics.py +40 -0
- goe/listener/schemas/orchestration.py +896 -0
- goe/listener/schemas/system.py +191 -0
- goe/listener/services/__init__.py +8 -0
- goe/listener/services/heartbeat.py +115 -0
- goe/listener/services/hybrid_view.py +356 -0
- goe/listener/services/orchestrate.py +24 -0
- goe/listener/services/periodic_tasks.py +196 -0
- goe/listener/services/system.py +126 -0
- goe/listener/utils/__init__.py +10 -0
- goe/listener/utils/cache.py +444 -0
- goe/listener/utils/group_by.py +26 -0
- goe/listener/utils/orchestrate.py +52 -0
- goe/listener/utils/ping.py +47 -0
- goe/listener/utils/system.py +83 -0
- goe/listener/worker.py +67 -0
- goe/listener/wsgi.py +130 -0
- goe/offload/__init__.py +0 -0
- goe/offload/backend_api.py +2081 -0
- goe/offload/backend_table.py +2645 -0
- goe/offload/bigquery/__init__.py +0 -0
- goe/offload/bigquery/bigquery_backend_api.py +2579 -0
- goe/offload/bigquery/bigquery_backend_table.py +664 -0
- goe/offload/bigquery/bigquery_column.py +167 -0
- goe/offload/bigquery/bigquery_literal.py +137 -0
- goe/offload/bigquery/bigquery_predicate.py +68 -0
- goe/offload/column_metadata.py +672 -0
- goe/offload/factory/__init__.py +0 -0
- goe/offload/factory/backend_api_factory.py +130 -0
- goe/offload/factory/backend_table_factory.py +162 -0
- goe/offload/factory/frontend_api_factory.py +96 -0
- goe/offload/factory/offload_source_table_factory.py +92 -0
- goe/offload/factory/offload_transport_factory.py +237 -0
- goe/offload/factory/offload_transport_rdbms_api_factory.py +64 -0
- goe/offload/factory/query_import_factory.py +45 -0
- goe/offload/factory/staging_file_factory.py +92 -0
- goe/offload/format_literal.py +70 -0
- goe/offload/frontend_api.py +882 -0
- goe/offload/hadoop/__init__.py +0 -0
- goe/offload/hadoop/hadoop_backend_api.py +1961 -0
- goe/offload/hadoop/hadoop_backend_table.py +898 -0
- goe/offload/hadoop/hadoop_column.py +151 -0
- goe/offload/hadoop/hadoop_predicate.py +82 -0
- goe/offload/hadoop/hive_backend_api.py +755 -0
- goe/offload/hadoop/hive_backend_table.py +257 -0
- goe/offload/hadoop/hive_literal.py +91 -0
- goe/offload/hadoop/impala_backend_api.py +1215 -0
- goe/offload/hadoop/impala_backend_table.py +273 -0
- goe/offload/hadoop/impala_literal.py +68 -0
- goe/offload/hadoop/sqoop_offload_transport.py +612 -0
- goe/offload/microsoft/__init__.py +0 -0
- goe/offload/microsoft/mssql_column.py +228 -0
- goe/offload/microsoft/mssql_frontend_api.py +650 -0
- goe/offload/microsoft/mssql_offload_source_table.py +735 -0
- goe/offload/microsoft/mssql_offload_transport_rdbms_api.py +204 -0
- goe/offload/microsoft/synapse_backend_api.py +2890 -0
- goe/offload/microsoft/synapse_backend_table.py +663 -0
- goe/offload/microsoft/synapse_column.py +220 -0
- goe/offload/microsoft/synapse_constants.py +29 -0
- goe/offload/microsoft/synapse_literal.py +156 -0
- goe/offload/microsoft/synapse_predicate.py +71 -0
- goe/offload/offload.py +706 -0
- goe/offload/offload_constants.py +605 -0
- goe/offload/offload_functions.py +588 -0
- goe/offload/offload_messages.py +708 -0
- goe/offload/offload_metadata_functions.py +462 -0
- goe/offload/offload_source_data.py +3257 -0
- goe/offload/offload_source_table.py +1295 -0
- goe/offload/offload_status_report.py +4038 -0
- goe/offload/offload_transport.py +2873 -0
- goe/offload/offload_transport_functions.py +466 -0
- goe/offload/offload_transport_rdbms_api.py +289 -0
- goe/offload/offload_validation.py +1052 -0
- goe/offload/offload_xform_functions.py +100 -0
- goe/offload/operation/__init__.py +0 -0
- goe/offload/operation/data_type_controls.py +365 -0
- goe/offload/operation/ddl_file.py +125 -0
- goe/offload/operation/not_null_columns.py +107 -0
- goe/offload/operation/partition_controls.py +521 -0
- goe/offload/operation/sort_columns.py +152 -0
- goe/offload/operation/stats_controls.py +425 -0
- goe/offload/operation/table_structure_checks.py +181 -0
- goe/offload/operation/transport.py +167 -0
- goe/offload/option_validation.py +217 -0
- goe/offload/oracle/__init__.py +0 -0
- goe/offload/oracle/oracle_column.py +326 -0
- goe/offload/oracle/oracle_frontend_api.py +1200 -0
- goe/offload/oracle/oracle_literal.py +129 -0
- goe/offload/oracle/oracle_offload_source_table.py +1765 -0
- goe/offload/oracle/oracle_offload_transport_rdbms_api.py +1106 -0
- goe/offload/oracle/oracle_predicate.py +129 -0
- goe/offload/predicate_offload.py +742 -0
- goe/offload/query_import_interface.py +92 -0
- goe/offload/snowflake/__init__.py +0 -0
- goe/offload/snowflake/snowflake_backend_api.py +2326 -0
- goe/offload/snowflake/snowflake_backend_table.py +747 -0
- goe/offload/snowflake/snowflake_column.py +161 -0
- goe/offload/snowflake/snowflake_literal.py +124 -0
- goe/offload/snowflake/snowflake_predicate.py +67 -0
- goe/offload/spark/__init__.py +0 -0
- goe/offload/spark/dataproc_offload_transport.py +615 -0
- goe/offload/spark/livy_offload_transport.py +551 -0
- goe/offload/spark/offload_transport_livy_requests.py +113 -0
- goe/offload/spark/pyspark_literal.py +69 -0
- goe/offload/spark/spark_thrift_backend_api.py +257 -0
- goe/offload/staging/__init__.py +0 -0
- goe/offload/staging/avro/__init__.py +0 -0
- goe/offload/staging/avro/avro_column.py +108 -0
- goe/offload/staging/avro/avro_staging_file.py +416 -0
- goe/offload/staging/parquet/__init__.py +0 -0
- goe/offload/staging/parquet/parquet_column.py +113 -0
- goe/offload/staging/parquet/parquet_staging_file.py +311 -0
- goe/offload/staging/staging_file.py +155 -0
- goe/offload/synthetic_partition_literal.py +249 -0
- goe/offload/teradata/__init__.py +0 -0
- goe/offload/teradata/teradata_column.py +309 -0
- goe/offload/teradata/teradata_frontend_api.py +908 -0
- goe/offload/teradata/teradata_literal.py +162 -0
- goe/offload/teradata/teradata_offload_source_table.py +1192 -0
- goe/offload/teradata/teradata_offload_transport_rdbms_api.py +620 -0
- goe/offload/teradata/teradata_partition_expression.py +205 -0
- goe/offload/teradata/teradata_predicate.py +86 -0
- goe/orchestration/__init__.py +0 -0
- goe/orchestration/cli_entry_points.py +79 -0
- goe/orchestration/command_steps.py +126 -0
- goe/orchestration/execution_id.py +101 -0
- goe/orchestration/orchestration_constants.py +56 -0
- goe/orchestration/orchestration_lock.py +164 -0
- goe/orchestration/orchestration_runner.py +539 -0
- goe/persistence/__init__.py +0 -0
- goe/persistence/factory/__init__.py +0 -0
- goe/persistence/factory/orchestration_repo_client_factory.py +48 -0
- goe/persistence/oracle/__init__.py +0 -0
- goe/persistence/oracle/oracle_orchestration_repo_client.py +1093 -0
- goe/persistence/orchestration_metadata.py +449 -0
- goe/persistence/orchestration_repo_client.py +436 -0
- goe/persistence/teradata/__init__.py +0 -0
- goe/persistence/teradata/teradata_orchestration_repo_client.py +358 -0
- goe/schema_sync/__init__.py +0 -0
- goe/schema_sync/schema_sync.py +170 -0
- goe/schema_sync/schema_sync_analyzer.py +663 -0
- goe/schema_sync/schema_sync_command_file.py +94 -0
- goe/schema_sync/schema_sync_constants.py +38 -0
- goe/schema_sync/schema_sync_processor.py +109 -0
- goe/schema_sync/schema_sync_step.py +91 -0
- goe/schema_sync/steps/__init__.py +0 -0
- goe/schema_sync/steps/add_backend_column.py +107 -0
- goe/schema_sync/steps/add_oracle_column.py +101 -0
- goe/schema_sync/steps/step_builder.py +30 -0
- goe/scripts/__init__.py +0 -0
- goe/scripts/agg_validate.py +288 -0
- goe/util/__init__.py +0 -0
- goe/util/avro_encoder.py +258 -0
- goe/util/better_impyla.py +1748 -0
- goe/util/exception_trigger.py +52 -0
- goe/util/goe_log.py +152 -0
- goe/util/goe_log_fh.py +67 -0
- goe/util/goe_version.py +18 -0
- goe/util/hive_ddl_transform.py +396 -0
- goe/util/hive_table_stats.py +1590 -0
- goe/util/hs2_connection.py +310 -0
- goe/util/json_tools.py +60 -0
- goe/util/local_cache.py +312 -0
- goe/util/misc_functions.py +775 -0
- goe/util/ora_query.py +417 -0
- goe/util/parallel_exec.py +222 -0
- goe/util/parquet_encoder.py +206 -0
- goe/util/password_tools.py +261 -0
- goe/util/polling_thread.py +97 -0
- goe/util/redis_tools.py +443 -0
- goe/util/retry_logic.py +165 -0
- goe/util/simple_timer.py +49 -0
- goe_framework-1.0.6.dist-info/AUTHORS +10 -0
- goe_framework-1.0.6.dist-info/LICENSE +201 -0
- goe_framework-1.0.6.dist-info/METADATA +210 -0
- goe_framework-1.0.6.dist-info/RECORD +239 -0
- goe_framework-1.0.6.dist-info/WHEEL +5 -0
- goe_framework-1.0.6.dist-info/top_level.txt +1 -0
goe/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright 2024 The GOE Authors. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import importlib.metadata
|
|
16
|
+
|
|
17
|
+
__version__ = importlib.metadata.version("goe-framework")
|
|
File without changes
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright 2016 The GOE Authors. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
""" HybridViewService: API over orchestration code that fetches information based on a hybrid view
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
|
|
23
|
+
from goe.config.orchestration_config import OrchestrationConfig
|
|
24
|
+
from goe.offload.factory.backend_api_factory import backend_api_factory
|
|
25
|
+
from goe.offload.factory.backend_table_factory import backend_table_factory
|
|
26
|
+
from goe.offload.factory.offload_source_table_factory import OffloadSourceTable
|
|
27
|
+
from goe.offload.offload_constants import LOG_LEVEL_DEBUG
|
|
28
|
+
from goe.offload.offload_messages import OffloadMessages, VVERBOSE
|
|
29
|
+
from goe.offload.offload_validation import (
|
|
30
|
+
CrossDbValidator,
|
|
31
|
+
build_verification_clauses,
|
|
32
|
+
)
|
|
33
|
+
from goe.persistence.orchestration_metadata import (
|
|
34
|
+
OrchestrationMetadata,
|
|
35
|
+
INCREMENTAL_PREDICATE_TYPE_LIST,
|
|
36
|
+
INCREMENTAL_PREDICATE_TYPE_LIST_AS_RANGE,
|
|
37
|
+
INCREMENTAL_PREDICATE_TYPE_RANGE,
|
|
38
|
+
)
|
|
39
|
+
from goe.util.simple_timer import SimpleTimer
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
###############################################################################
|
|
43
|
+
# EXCEPTIONS
|
|
44
|
+
###############################################################################
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HybridViewServiceException(Exception):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
###############################################################################
|
|
52
|
+
# CONSTANTS
|
|
53
|
+
###############################################################################
|
|
54
|
+
|
|
55
|
+
JSON_KEY_BACKEND_NUM_ROWS = "num_rows"
|
|
56
|
+
JSON_KEY_BACKEND_SIZE = "size_in_bytes"
|
|
57
|
+
JSON_KEY_BACKEND_PARTITIONS = "partitions"
|
|
58
|
+
JSON_KEY_BACKEND_PARTITIONING = "partitioning_supported"
|
|
59
|
+
JSON_ALL_BACKEND_KEYS = [
|
|
60
|
+
JSON_KEY_BACKEND_NUM_ROWS,
|
|
61
|
+
JSON_KEY_BACKEND_SIZE,
|
|
62
|
+
JSON_KEY_BACKEND_PARTITIONS,
|
|
63
|
+
JSON_KEY_BACKEND_PARTITIONING,
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
JSON_KEY_VALIDATE_STATUS = "success"
|
|
67
|
+
JSON_KEY_VALIDATE_MESSAGE = "message"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
###############################################################################
|
|
71
|
+
# LOGGING
|
|
72
|
+
###############################################################################
|
|
73
|
+
|
|
74
|
+
logger = logging.getLogger(__name__)
|
|
75
|
+
# Disabling logging by default
|
|
76
|
+
logger.addHandler(logging.NullHandler())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
###############################################################################
|
|
80
|
+
# HybridViewService
|
|
81
|
+
###############################################################################
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class HybridViewService(object):
|
|
85
|
+
"""API over orchestration code that fetches information based on a hybrid view"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, hybrid_schema, hybrid_view, dry_run=False, existing_log_fh=None):
|
|
88
|
+
raise NotImplementedError(
|
|
89
|
+
"Metadata is no longer keyed on a hybrid view, the viability of Conductor itself needs to be reviewed."
|
|
90
|
+
)
|
|
91
|
+
assert hybrid_schema and hybrid_view
|
|
92
|
+
self._dry_run = dry_run
|
|
93
|
+
self._connection_options = self._build_connection_options()
|
|
94
|
+
self._messages = OffloadMessages(log_fh=existing_log_fh)
|
|
95
|
+
if (
|
|
96
|
+
self._connection_options.log_level == LOG_LEVEL_DEBUG
|
|
97
|
+
and not existing_log_fh
|
|
98
|
+
):
|
|
99
|
+
self._messages.init_log(
|
|
100
|
+
self._connection_options.log_path, "hybrid_view_service"
|
|
101
|
+
)
|
|
102
|
+
self._debug(
|
|
103
|
+
"Initializing HybridViewService(%s.%s)" % (hybrid_schema, hybrid_view)
|
|
104
|
+
)
|
|
105
|
+
self._offload_metadata = OrchestrationMetadata.from_name(
|
|
106
|
+
hybrid_schema,
|
|
107
|
+
hybrid_view,
|
|
108
|
+
connection_options=self._connection_options,
|
|
109
|
+
messages=self._messages,
|
|
110
|
+
)
|
|
111
|
+
if not self._offload_metadata:
|
|
112
|
+
self._debug("Hybrid view has no metadata")
|
|
113
|
+
raise HybridViewServiceException(
|
|
114
|
+
"Hybrid view has no metadata: %s.%s" % (hybrid_schema, hybrid_view)
|
|
115
|
+
)
|
|
116
|
+
self._debug("Hybrid View metadata:\n%r" % self._offload_metadata)
|
|
117
|
+
self.hybrid_schema = self._offload_metadata.hybrid_owner
|
|
118
|
+
self.hybrid_view = self._offload_metadata.hybrid_view
|
|
119
|
+
self._frontend_table_owner = self._offload_metadata.offloaded_owner
|
|
120
|
+
self._frontend_table_name = self._offload_metadata.offloaded_table
|
|
121
|
+
self._backend_table_owner = self._offload_metadata.backend_owner
|
|
122
|
+
self._backend_table_name = self._offload_metadata.backend_table
|
|
123
|
+
self._ipa_predicate_type = self._offload_metadata.incremental_predicate_type
|
|
124
|
+
self._incremental_keys = self._offload_metadata.incremental_key
|
|
125
|
+
# Initially store in state as None to allow lazy collection via _get_backend_detail()
|
|
126
|
+
self._backend_info = None
|
|
127
|
+
# Initially store in state as None to allow lazy collection via _get_frontend_detail()
|
|
128
|
+
self._frontend_info = None
|
|
129
|
+
|
|
130
|
+
###########################################################################
|
|
131
|
+
# PRIVATE METHODS
|
|
132
|
+
###########################################################################
|
|
133
|
+
|
|
134
|
+
def _backend_table_does_not_exist_exception(self):
|
|
135
|
+
raise HybridViewServiceException(
|
|
136
|
+
"Backend table does not exist: %s.%s"
|
|
137
|
+
% (self._backend_table_owner, self._backend_table_name)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def _bind_in_predicates(self):
|
|
141
|
+
return bool(
|
|
142
|
+
self._ipa_predicate_type
|
|
143
|
+
in [
|
|
144
|
+
INCREMENTAL_PREDICATE_TYPE_RANGE,
|
|
145
|
+
INCREMENTAL_PREDICATE_TYPE_LIST_AS_RANGE,
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def _build_connection_options(self):
|
|
150
|
+
config = OrchestrationConfig.as_defaults({})
|
|
151
|
+
return config
|
|
152
|
+
|
|
153
|
+
def _debug(self, msg):
|
|
154
|
+
self._messages.log(msg, detail=VVERBOSE)
|
|
155
|
+
logger.debug(msg)
|
|
156
|
+
|
|
157
|
+
def _decode_high_values(self, frontend_table, lower_hv_str=None, upper_hv_str=None):
|
|
158
|
+
logger.debug("_decode_high_values(%r, %r)" % (upper_hv_str, lower_hv_str))
|
|
159
|
+
upper_hvs = None
|
|
160
|
+
if upper_hv_str:
|
|
161
|
+
self._debug("Decoding HV string: %r" % upper_hv_str)
|
|
162
|
+
upper_hvs = frontend_table.decode_partition_high_values(upper_hv_str)
|
|
163
|
+
self._debug("Decoded to: %s" % repr(upper_hvs))
|
|
164
|
+
prior_hvs = None
|
|
165
|
+
if lower_hv_str:
|
|
166
|
+
self._debug("Decoding HV string: %r" % lower_hv_str)
|
|
167
|
+
prior_hvs = frontend_table.decode_partition_high_values(lower_hv_str)
|
|
168
|
+
self._debug("Decoded to: %s" % repr(prior_hvs))
|
|
169
|
+
return upper_hvs, prior_hvs
|
|
170
|
+
|
|
171
|
+
def _format_payload(self, payload, as_json):
|
|
172
|
+
if as_json:
|
|
173
|
+
json_payload = json.dumps(payload)
|
|
174
|
+
return json_payload
|
|
175
|
+
else:
|
|
176
|
+
return payload
|
|
177
|
+
|
|
178
|
+
def _get_backend_api(self):
|
|
179
|
+
logger.debug("_get_backend_api(%s)" % self._connection_options.target)
|
|
180
|
+
return backend_api_factory(
|
|
181
|
+
self._connection_options.target,
|
|
182
|
+
self._connection_options,
|
|
183
|
+
self._messages,
|
|
184
|
+
dry_run=self._dry_run,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def _get_backend_table(self):
|
|
188
|
+
logger.debug("_get_backend_table(%s)" % self._connection_options.target)
|
|
189
|
+
return backend_table_factory(
|
|
190
|
+
self._backend_table_owner,
|
|
191
|
+
self._backend_table_name,
|
|
192
|
+
self._connection_options.target,
|
|
193
|
+
self._connection_options,
|
|
194
|
+
self._messages,
|
|
195
|
+
hybrid_metadata=self._offload_metadata,
|
|
196
|
+
dry_run=self._dry_run,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def _get_backend_detail(self, attribute_name=None):
|
|
200
|
+
logger.debug("_get_backend_detail(%s)" % attribute_name)
|
|
201
|
+
if not self._backend_info:
|
|
202
|
+
self._debug("Initializing backend attributes")
|
|
203
|
+
backend_table = self._get_backend_table()
|
|
204
|
+
if not backend_table.exists():
|
|
205
|
+
self._backend_table_does_not_exist_exception()
|
|
206
|
+
size_in_bytes, num_rows = backend_table.get_table_size_and_row_count()
|
|
207
|
+
backend_partitions = backend_table.get_table_partitions()
|
|
208
|
+
self._backend_info = {
|
|
209
|
+
"columns": backend_table.get_columns(),
|
|
210
|
+
JSON_KEY_BACKEND_SIZE: size_in_bytes,
|
|
211
|
+
JSON_KEY_BACKEND_NUM_ROWS: num_rows,
|
|
212
|
+
JSON_KEY_BACKEND_PARTITIONS: backend_partitions,
|
|
213
|
+
JSON_KEY_BACKEND_PARTITIONING: backend_table.partition_by_column_supported(),
|
|
214
|
+
}
|
|
215
|
+
if attribute_name:
|
|
216
|
+
return self._backend_info[attribute_name]
|
|
217
|
+
else:
|
|
218
|
+
return self._backend_info
|
|
219
|
+
|
|
220
|
+
def _get_frontend_table(self):
|
|
221
|
+
logger.debug(
|
|
222
|
+
"_get_frontend_table(%s.%s)"
|
|
223
|
+
% (self._frontend_table_owner, self._frontend_table_name)
|
|
224
|
+
)
|
|
225
|
+
return OffloadSourceTable.create(
|
|
226
|
+
self._frontend_table_owner,
|
|
227
|
+
self._frontend_table_name,
|
|
228
|
+
self._connection_options,
|
|
229
|
+
self._messages,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def _get_frontend_detail(self, attribute_name):
|
|
233
|
+
logger.debug("_get_frontend_detail(%s)" % attribute_name)
|
|
234
|
+
assert attribute_name
|
|
235
|
+
if not self._frontend_info:
|
|
236
|
+
self._debug("Initializing frontend attributes")
|
|
237
|
+
frontend_table = OffloadSourceTable.create(
|
|
238
|
+
self._frontend_table_owner,
|
|
239
|
+
self._frontend_table_name,
|
|
240
|
+
self._connection_options,
|
|
241
|
+
self._messages,
|
|
242
|
+
offload_by_subpartition=self._offload_metadata.is_subpartition_offload(),
|
|
243
|
+
)
|
|
244
|
+
columns = frontend_table.columns
|
|
245
|
+
self._frontend_info = {"columns": columns}
|
|
246
|
+
return self._frontend_info[attribute_name]
|
|
247
|
+
|
|
248
|
+
###########################################################################
|
|
249
|
+
# PUBLIC METHODS
|
|
250
|
+
###########################################################################
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def backend_partition_by_column_supported(self):
|
|
254
|
+
"""Does the backend support partitioning, strictly speaking we don't need this info except for testing"""
|
|
255
|
+
return self._get_backend_detail(JSON_KEY_BACKEND_PARTITIONING)
|
|
256
|
+
|
|
257
|
+
def backend_attributes(self, as_json=True):
|
|
258
|
+
"""Return a single dict or JSON document containing information from backend system.
|
|
259
|
+
partition_key_values results in a backend query which can take some time so is excluded by default.
|
|
260
|
+
"""
|
|
261
|
+
payload = self._get_backend_detail()
|
|
262
|
+
filtered_payload = {
|
|
263
|
+
k: v for k, v in payload.items() if k in JSON_ALL_BACKEND_KEYS
|
|
264
|
+
}
|
|
265
|
+
return self._format_payload(filtered_payload, as_json)
|
|
266
|
+
|
|
267
|
+
def validate_by_aggregation(self, lower_hv=None, upper_hv=None, as_json=True):
|
|
268
|
+
"""Return response from CrossDbValidator as JSON: {'success': True/False, 'message': "blah"}
|
|
269
|
+
lower_hv, upper_hv: DBA_TAB_PARTITIONS.HIGH_VALUE based. Can have both empty, upper_hv only or both.
|
|
270
|
+
"""
|
|
271
|
+
if lower_hv:
|
|
272
|
+
assert isinstance(lower_hv, str)
|
|
273
|
+
if upper_hv:
|
|
274
|
+
assert isinstance(upper_hv, str)
|
|
275
|
+
logger.debug("validate_by_aggregation(%r, %r)" % (upper_hv, lower_hv))
|
|
276
|
+
if not self._frontend_table_owner or not self._frontend_table_name:
|
|
277
|
+
raise HybridViewServiceException(
|
|
278
|
+
"Hybrid view is not for an offloaded table: %s.%s"
|
|
279
|
+
% (self.hybrid_schema, self.hybrid_view)
|
|
280
|
+
)
|
|
281
|
+
self._debug("Running Hybrid View validation by aggregation")
|
|
282
|
+
t = SimpleTimer("validate_by_aggregation")
|
|
283
|
+
backend_table = self._get_backend_table()
|
|
284
|
+
if not backend_table.exists():
|
|
285
|
+
self._backend_table_does_not_exist_exception()
|
|
286
|
+
frontend_table = self._get_frontend_table()
|
|
287
|
+
upper_hvs, prior_hvs = self._decode_high_values(
|
|
288
|
+
frontend_table, lower_hv_str=lower_hv, upper_hv_str=upper_hv
|
|
289
|
+
)
|
|
290
|
+
if self._ipa_predicate_type == INCREMENTAL_PREDICATE_TYPE_LIST and upper_hvs:
|
|
291
|
+
# HVs for LIST are lists of tuples, not just a tuple as per RANGE
|
|
292
|
+
upper_hvs = [upper_hvs]
|
|
293
|
+
frontend_filters, query_binds = build_verification_clauses(
|
|
294
|
+
frontend_table,
|
|
295
|
+
self._ipa_predicate_type,
|
|
296
|
+
upper_hvs,
|
|
297
|
+
prior_hvs,
|
|
298
|
+
with_binds=self._bind_in_predicates(),
|
|
299
|
+
)
|
|
300
|
+
self._debug("Generated frontend filters:\n%r" % frontend_filters)
|
|
301
|
+
backend_filters, _ = build_verification_clauses(
|
|
302
|
+
frontend_table,
|
|
303
|
+
self._ipa_predicate_type,
|
|
304
|
+
upper_hvs,
|
|
305
|
+
prior_hvs,
|
|
306
|
+
with_binds=False,
|
|
307
|
+
backend_table=backend_table,
|
|
308
|
+
)
|
|
309
|
+
self._debug("Generated backend filters:\n%r" % backend_filters)
|
|
310
|
+
|
|
311
|
+
validator = CrossDbValidator(
|
|
312
|
+
self._frontend_table_owner,
|
|
313
|
+
self._frontend_table_name,
|
|
314
|
+
self._connection_options,
|
|
315
|
+
backend_table.get_backend_api(),
|
|
316
|
+
messages=self._messages,
|
|
317
|
+
backend_db=self._backend_table_owner,
|
|
318
|
+
backend_table=self._backend_table_name,
|
|
319
|
+
execute=(not self._dry_run),
|
|
320
|
+
)
|
|
321
|
+
status, agg_msg = validator.validate(
|
|
322
|
+
safe=False,
|
|
323
|
+
filters=backend_filters,
|
|
324
|
+
execute=bool(not self._dry_run),
|
|
325
|
+
frontend_filters=frontend_filters,
|
|
326
|
+
frontend_query_params=query_binds,
|
|
327
|
+
)
|
|
328
|
+
self._debug("validate_by_aggregation returning: %s" % status)
|
|
329
|
+
self._debug(t.show())
|
|
330
|
+
payload = {JSON_KEY_VALIDATE_STATUS: status, JSON_KEY_VALIDATE_MESSAGE: agg_msg}
|
|
331
|
+
return self._format_payload(payload, as_json)
|
|
332
|
+
|
|
333
|
+
def validate_by_count(self, lower_hv=None, upper_hv=None, as_json=True):
|
|
334
|
+
raise NotImplementedError("Validate by hybrid view is no longer imeplemented")
|
goe/config/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Copyright 2024 The GOE Authors. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from dotenv import load_dotenv
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
CONFIG_FILE_NAME = "offload.env"
|
|
24
|
+
KEY_VALUE_PATTERN = re.compile(r"#?[ ]*([A-Z_0-9]+)=(.*)")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_config_path():
|
|
28
|
+
"""Check OFFLOAD_HOME in top level command wrappers"""
|
|
29
|
+
if not os.environ.get("OFFLOAD_HOME"):
|
|
30
|
+
print("OFFLOAD_HOME environment variable missing")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_environment_file_path():
|
|
35
|
+
return os.path.join(os.environ.get("OFFLOAD_HOME"), "conf", CONFIG_FILE_NAME)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_env(path: str = None):
|
|
39
|
+
"""Load GOE environment from a configuration file.
|
|
40
|
+
|
|
41
|
+
By default this is a fixed location: $OFFLOAD_HOME/conf/offload.env.
|
|
42
|
+
In time this will become a parameter and support cloud storage locations.
|
|
43
|
+
"""
|
|
44
|
+
if not path:
|
|
45
|
+
path = get_environment_file_path()
|
|
46
|
+
|
|
47
|
+
load_dotenv(path)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def env_key_value_pair(line_from_file: str) -> Optional[tuple]:
|
|
51
|
+
"""Used by connect to get the key names from a configuration file"""
|
|
52
|
+
m = KEY_VALUE_PATTERN.match(line_from_file)
|
|
53
|
+
return m.groups() if m else None
|