| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent c2bbb22 commit 0c4c5da
13 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -736,6 +736,10 @@ def connect( | |||
| 736 | 736 | route_to_leader_enabled=True, | |
| 737 | 737 | database_role=None, | |
| 738 | 738 | experimental_host=None, | |
| 739 | + use_plain_text=False, | ||
| 740 | + ca_certificate=None, | ||
| 741 | + client_certificate=None, | ||
| 742 | + client_key=None, | ||
| 739 | 743 | **kwargs, | |
| 740 | 744 | ): | |
| 741 | 745 | """Creates a connection to a Google Cloud Spanner database. | |
@@ -789,6 +793,28 @@ def connect( | |||
| 789 | 793 | :rtype: :class:`google.cloud.spanner_dbapi.connection.Connection` | |
| 790 | 794 | :returns: Connection object associated with the given Google Cloud Spanner | |
| 791 | 795 | resource. | |
| 796 | + | ||
| 797 | + :type experimental_host: str | ||
| 798 | + :param experimental_host: (Optional) The endpoint for a spanner experimental host deployment. | ||
| 799 | + This is intended only for experimental host spanner endpoints. | ||
| 800 | + | ||
| 801 | + :type use_plain_text: bool | ||
| 802 | + :param use_plain_text: (Optional) Whether to use plain text for the connection. | ||
| 803 | + This is intended only for experimental host spanner endpoints. | ||
| 804 | + If not set, the default behavior is to use TLS. | ||
| 805 | + | ||
| 806 | + :type ca_certificate: str | ||
| 807 | + :param ca_certificate: (Optional) The path to the CA certificate file used for TLS connection. | ||
| 808 | + This is intended only for experimental host spanner endpoints. | ||
| 809 | + This is mandatory if the experimental_host requires a TLS connection. | ||
| 810 | + :type client_certificate: str | ||
| 811 | + :param client_certificate: (Optional) The path to the client certificate file used for mTLS connection. | ||
| 812 | + This is intended only for experimental host spanner endpoints. | ||
| 813 | + This is mandatory if the experimental_host requires an mTLS connection. | ||
| 814 | + :type client_key: str | ||
| 815 | + :param client_key: (Optional) The path to the client key file used for mTLS connection. | ||
| 816 | + This is intended only for experimental host spanner endpoints. | ||
| 817 | + This is mandatory if the experimental_host requires an mTLS connection. | ||
| 792 | 818 | """ | |
| 793 | 819 | if client is None: | |
| 794 | 820 | client_info = ClientInfo( | |
@@ -817,6 +843,10 @@ def connect( | |||
| 817 | 843 | client_info=client_info, | |
| 818 | 844 | route_to_leader_enabled=route_to_leader_enabled, | |
| 819 | 845 | client_options=client_options, | |
| 846 | + use_plain_text=use_plain_text, | ||
| 847 | + ca_certificate=ca_certificate, | ||
| 848 | + client_certificate=client_certificate, | ||
| 849 | + client_key=client_key, | ||
| 820 | 850 | ) | |
| 821 | 851 | else: | |
| 822 | 852 | if project is not None and client.project != project: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -868,3 +868,65 @@ def _merge_Transaction_Options( | |||
| 868 | 868 | ||
| 869 | 869 | # Convert protobuf object back into a TransactionOptions instance | |
| 870 | 870 | return TransactionOptions(merged_pb) | |
| 871 | + | ||
| 872 | + | ||
| 873 | + def _create_experimental_host_transport( | ||
| 874 | + transport_factory, | ||
| 875 | + experimental_host, | ||
| 876 | + use_plain_text, | ||
| 877 | + ca_certificate, | ||
| 878 | + client_certificate, | ||
| 879 | + client_key, | ||
| 880 | + interceptors=None, | ||
| 881 | + ): | ||
| 882 | + """Creates an experimental host transport for Spanner. | ||
| 883 | + | ||
| 884 | + Args: | ||
| 885 | + transport_factory (type): The transport class to instantiate (e.g. | ||
| 886 | + `SpannerGrpcTransport`). | ||
| 887 | + experimental_host (str): The endpoint for the experimental host. | ||
| 888 | + use_plain_text (bool): Whether to use a plain text (insecure) connection. | ||
| 889 | + ca_certificate (str): Path to the CA certificate file for TLS. | ||
| 890 | + client_certificate (str): Path to the client certificate file for mTLS. | ||
| 891 | + client_key (str): Path to the client key file for mTLS. | ||
| 892 | + interceptors (list): Optional list of interceptors to add to the channel. | ||
| 893 | + | ||
| 894 | + Returns: | ||
| 895 | + object: An instance of the transport class created by `transport_factory`. | ||
| 896 | + | ||
| 897 | + Raises: | ||
| 898 | + ValueError: If TLS/mTLS configuration is invalid. | ||
| 899 | + """ | ||
| 900 | + import grpc | ||
| 901 | + from google.auth.credentials import AnonymousCredentials | ||
| 902 | + | ||
| 903 | + channel = None | ||
| 904 | + if use_plain_text: | ||
| 905 | + channel = grpc.insecure_channel(target=experimental_host) | ||
| 906 | + elif ca_certificate: | ||
| 907 | + with open(ca_certificate, "rb") as f: | ||
| 908 | + ca_cert = f.read() | ||
| 909 | + if client_certificate and client_key: | ||
| 910 | + with open(client_certificate, "rb") as f: | ||
| 911 | + client_cert = f.read() | ||
| 912 | + with open(client_key, "rb") as f: | ||
| 913 | + private_key = f.read() | ||
| 914 | + ssl_creds = grpc.ssl_channel_credentials( | ||
| 915 | + root_certificates=ca_cert, | ||
| 916 | + private_key=private_key, | ||
| 917 | + certificate_chain=client_cert, | ||
| 918 | + ) | ||
| 919 | + elif client_certificate or client_key: | ||
| 920 | + raise ValueError( | ||
| 921 | + "Both client_certificate and client_key must be provided for mTLS connection" | ||
| 922 | + ) | ||
| 923 | + else: | ||
| 924 | + ssl_creds = grpc.ssl_channel_credentials(root_certificates=ca_cert) | ||
| 925 | + channel = grpc.secure_channel(experimental_host, ssl_creds) | ||
| 926 | + else: | ||
| 927 | + raise ValueError( | ||
| 928 | + "TLS/mTLS connection requires ca_certificate to be set for experimental_host" | ||
| 929 | + ) | ||
| 930 | + if interceptors is not None: | ||
| 931 | + channel = grpc.intercept_channel(channel, *interceptors) | ||
| 932 | + return transport_factory(channel=channel, credentials=AnonymousCredentials()) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -50,7 +50,10 @@ | |||
| 50 | 50 | from google.cloud.spanner_v1 import __version__ | |
| 51 | 51 | from google.cloud.spanner_v1 import ExecuteSqlRequest | |
| 52 | 52 | from google.cloud.spanner_v1 import DefaultTransactionOptions | |
| 53 | - from google.cloud.spanner_v1._helpers import _merge_query_options | ||
| 53 | + from google.cloud.spanner_v1._helpers import ( | ||
| 54 | + _create_experimental_host_transport, | ||
| 55 | + _merge_query_options, | ||
| 56 | + ) | ||
| 54 | 57 | from google.cloud.spanner_v1._helpers import _metadata_with_prefix | |
| 55 | 58 | from google.cloud.spanner_v1.instance import Instance | |
| 56 | 59 | from google.cloud.spanner_v1.metrics.constants import ( | |
@@ -227,6 +230,30 @@ class Client(ClientWithProject): | |||
| 227 | 230 | ||
| 228 | 231 | :raises: :class:`ValueError <exceptions.ValueError>` if both ``read_only`` | |
| 229 | 232 | and ``admin`` are :data:`True` | |
| 233 | + | ||
| 234 | + :type use_plain_text: bool | ||
| 235 | + :param use_plain_text: (Optional) Whether to use plain text for the connection. | ||
| 236 | + This is intended only for experimental host spanner endpoints. | ||
| 237 | + If set, this will override the `api_endpoint` in `client_options`. | ||
| 238 | + If not set, the default behavior is to use TLS. | ||
| 239 | + | ||
| 240 | + :type ca_certificate: str | ||
| 241 | + :param ca_certificate: (Optional) The path to the CA certificate file used for TLS connection. | ||
| 242 | + This is intended only for experimental host spanner endpoints. | ||
| 243 | + If set, this will override the `api_endpoint` in `client_options`. | ||
| 244 | + This is mandatory if the experimental_host requires a TLS connection. | ||
| 245 | + | ||
| 246 | + :type client_certificate: str | ||
| 247 | + :param client_certificate: (Optional) The path to the client certificate file used for mTLS connection. | ||
| 248 | + This is intended only for experimental host spanner endpoints. | ||
| 249 | + If set, this will override the `api_endpoint` in `client_options`. | ||
| 250 | + This is mandatory if the experimental_host requires a mTLS connection. | ||
| 251 | + | ||
| 252 | + :type client_key: str | ||
| 253 | + :param client_key: (Optional) The path to the client key file used for mTLS connection. | ||
| 254 | + This is intended only for experimental host spanner endpoints. | ||
| 255 | + If set, this will override the `api_endpoint` in `client_options`. | ||
| 256 | + This is mandatory if the experimental_host requires a mTLS connection. | ||
| 230 | 257 | """ | |
| 231 | 258 | ||
| 232 | 259 | _instance_admin_api = None | |
@@ -251,6 +278,10 @@ def __init__( | |||
| 251 | 278 | default_transaction_options: Optional[DefaultTransactionOptions] = None, | |
| 252 | 279 | experimental_host=None, | |
| 253 | 280 | disable_builtin_metrics=False, | |
| 281 | + use_plain_text=False, | ||
| 282 | + ca_certificate=None, | ||
| 283 | + client_certificate=None, | ||
| 284 | + client_key=None, | ||
| 254 | 285 | ): | |
| 255 | 286 | self._emulator_host = _get_spanner_emulator_host() | |
| 256 | 287 | self._experimental_host = experimental_host | |
@@ -265,6 +296,12 @@ def __init__( | |||
| 265 | 296 | if self._emulator_host: | |
| 266 | 297 | credentials = AnonymousCredentials() | |
| 267 | 298 | elif self._experimental_host: | |
| 299 | + # For all experimental host endpoints project is default | ||
| 300 | + project = "default" | ||
| 301 | + self._use_plain_text = use_plain_text | ||
| 302 | + self._ca_certificate = ca_certificate | ||
| 303 | + self._client_certificate = client_certificate | ||
| 304 | + self._client_key = client_key | ||
| 268 | 305 | credentials = AnonymousCredentials() | |
| 269 | 306 | elif isinstance(credentials, AnonymousCredentials): | |
| 270 | 307 | self._emulator_host = self._client_options.api_endpoint | |
@@ -361,8 +398,13 @@ def instance_admin_api(self): | |||
| 361 | 398 | transport=transport, | |
| 362 | 399 | ) | |
| 363 | 400 | elif self._experimental_host: | |
| 364 | - transport = InstanceAdminGrpcTransport( | ||
| 365 | - channel=grpc.insecure_channel(target=self._experimental_host) | ||
| 401 | + transport = _create_experimental_host_transport( | ||
| 402 | + InstanceAdminGrpcTransport, | ||
| 403 | + self._experimental_host, | ||
| 404 | + self._use_plain_text, | ||
| 405 | + self._ca_certificate, | ||
| 406 | + self._client_certificate, | ||
| 407 | + self._client_key, | ||
| 366 | 408 | ) | |
| 367 | 409 | self._instance_admin_api = InstanceAdminClient( | |
| 368 | 410 | client_info=self._client_info, | |
@@ -391,8 +433,13 @@ def database_admin_api(self): | |||
| 391 | 433 | transport=transport, | |
| 392 | 434 | ) | |
| 393 | 435 | elif self._experimental_host: | |
| 394 | - transport = DatabaseAdminGrpcTransport( | ||
| 395 | - channel=grpc.insecure_channel(target=self._experimental_host) | ||
| 436 | + transport = _create_experimental_host_transport( | ||
| 437 | + DatabaseAdminGrpcTransport, | ||
| 438 | + self._experimental_host, | ||
| 439 | + self._use_plain_text, | ||
| 440 | + self._ca_certificate, | ||
| 441 | + self._client_certificate, | ||
| 442 | + self._client_key, | ||
| 396 | 443 | ) | |
| 397 | 444 | self._database_admin_api = DatabaseAdminClient( | |
| 398 | 445 | client_info=self._client_info, | |
@@ -539,7 +586,6 @@ def instance( | |||
| 539 | 586 | self._emulator_host, | |
| 540 | 587 | labels, | |
| 541 | 588 | processing_units, | |
| 542 | - self._experimental_host, | ||
| 543 | 589 | ) | |
| 544 | 590 | ||
| 545 | 591 | def list_instances(self, filter_="", page_size=None): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -56,6 +56,7 @@ | |||
| 56 | 56 | _metadata_with_request_id, | |
| 57 | 57 | _augment_errors_with_request_id, | |
| 58 | 58 | _metadata_with_request_id_and_req_id, | |
| 59 | + _create_experimental_host_transport, | ||
| 59 | 60 | ) | |
| 60 | 61 | from google.cloud.spanner_v1.batch import Batch | |
| 61 | 62 | from google.cloud.spanner_v1.batch import MutationGroups | |
@@ -198,17 +199,15 @@ def __init__( | |||
| 198 | 199 | ) | |
| 199 | 200 | self._proto_descriptors = proto_descriptors | |
| 200 | 201 | self._channel_id = 0 # It'll be created when _spanner_api is created. | |
| 202 | + self._experimental_host = self._instance._client._experimental_host | ||
| 201 | 203 | ||
| 202 | 204 | if pool is None: | |
| 203 | 205 | pool = BurstyPool(database_role=database_role) | |
| 204 | 206 | ||
| 205 | 207 | self._pool = pool | |
| 206 | 208 | pool.bind(self) | |
| 207 | - is_experimental_host = self._instance.experimental_host is not None | ||
| 208 | 209 | ||
| 209 | - self._sessions_manager = DatabaseSessionsManager( | ||
| 210 | - self, pool, is_experimental_host | ||
| 211 | - ) | ||
| 210 | + self._sessions_manager = DatabaseSessionsManager(self, pool) | ||
| 212 | 211 | ||
| 213 | 212 | @classmethod | |
| 214 | 213 | def from_pb(cls, database_pb, instance, pool=None): | |
@@ -453,9 +452,14 @@ def spanner_api(self): | |||
| 453 | 452 | client_info=client_info, transport=transport | |
| 454 | 453 | ) | |
| 455 | 454 | return self._spanner_api | |
| 456 | - if self._instance.experimental_host is not None: | ||
| 457 | - transport = SpannerGrpcTransport( | ||
| 458 | - channel=grpc.insecure_channel(self._instance.experimental_host) | ||
| 455 | + if self._experimental_host is not None: | ||
| 456 | + transport = _create_experimental_host_transport( | ||
| 457 | + SpannerGrpcTransport, | ||
| 458 | + self._experimental_host, | ||
| 459 | + self._instance._client._use_plain_text, | ||
| 460 | + self._instance._client._ca_certificate, | ||
| 461 | + self._instance._client._client_certificate, | ||
| 462 | + self._instance._client._client_key, | ||
| 459 | 463 | ) | |
| 460 | 464 | self._spanner_api = SpannerClient( | |
| 461 | 465 | client_info=client_info, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -62,10 +62,9 @@ class DatabaseSessionsManager(object): | |||
| 62 | 62 | _MAINTENANCE_THREAD_POLLING_INTERVAL = timedelta(minutes=10) | |
| 63 | 63 | _MAINTENANCE_THREAD_REFRESH_INTERVAL = timedelta(days=7) | |
| 64 | 64 | ||
| 65 | - def __init__(self, database, pool, is_experimental_host: bool = False): | ||
| 65 | + def __init__(self, database, pool): | ||
| 66 | 66 | self._database = database | |
| 67 | 67 | self._pool = pool | |
| 68 | - self._is_experimental_host = is_experimental_host | ||
| 69 | 68 | ||
| 70 | 69 | # Declare multiplexed session attributes. When a multiplexed session for the | |
| 71 | 70 | # database session manager is created, a maintenance thread is initialized to | |
@@ -89,7 +88,8 @@ def get_session(self, transaction_type: TransactionType) -> Session: | |||
| 89 | 88 | ||
| 90 | 89 | session = ( | |
| 91 | 90 | self._get_multiplexed_session() | |
| 92 | - if self._use_multiplexed(transaction_type) or self._is_experimental_host | ||
| 91 | + if self._use_multiplexed(transaction_type) | ||
| 92 | + or self._database._experimental_host is not None | ||
| 93 | 93 | else self._pool.get() | |
| 94 | 94 | ) | |
| 95 | 95 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -122,7 +122,6 @@ def __init__( | |||
| 122 | 122 | emulator_host=None, | |
| 123 | 123 | labels=None, | |
| 124 | 124 | processing_units=None, | |
| 125 | - experimental_host=None, | ||
| 126 | 125 | ): | |
| 127 | 126 | self.instance_id = instance_id | |
| 128 | 127 | self._client = client | |
@@ -143,7 +142,6 @@ def __init__( | |||
| 143 | 142 | self._node_count = processing_units // PROCESSING_UNITS_PER_NODE | |
| 144 | 143 | self.display_name = display_name or instance_id | |
| 145 | 144 | self.emulator_host = emulator_host | |
| 146 | - self.experimental_host = experimental_host | ||
| 147 | 145 | if labels is None: | |
| 148 | 146 | labels = {} | |
| 149 | 147 | self.labels = labels | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -17,6 +17,7 @@ | |||
| 17 | 17 | import google.auth.credentials | |
| 18 | 18 | from google.cloud.spanner_admin_database_v1 import DatabaseDialect | |
| 19 | 19 | from google.cloud.spanner_v1 import SpannerClient | |
| 20 | + from google.cloud.spanner_v1._helpers import _create_experimental_host_transport | ||
| 20 | 21 | from google.cloud.spanner_v1.database import Database, SPANNER_DATA_SCOPE | |
| 21 | 22 | from google.cloud.spanner_v1.services.spanner.transports import ( | |
| 22 | 23 | SpannerGrpcTransport, | |
@@ -86,12 +87,18 @@ def spanner_api(self): | |||
| 86 | 87 | transport=transport, | |
| 87 | 88 | ) | |
| 88 | 89 | return self._spanner_api | |
| 89 | - if self._instance.experimental_host is not None: | ||
| 90 | - channel = grpc.insecure_channel(self._instance.experimental_host) | ||
| 90 | + if self._experimental_host is not None: | ||
| 91 | 91 | self._x_goog_request_id_interceptor = XGoogRequestIDHeaderInterceptor() | |
| 92 | 92 | self._interceptors.append(self._x_goog_request_id_interceptor) | |
| 93 | - channel = grpc.intercept_channel(channel, *self._interceptors) | ||
| 94 | - transport = SpannerGrpcTransport(channel=channel) | ||
| 93 | + transport = _create_experimental_host_transport( | ||
| 94 | + SpannerGrpcTransport, | ||
| 95 | + self._experimental_host, | ||
| 96 | + self._instance._client._use_plain_text, | ||
| 97 | + self._instance._client._ca_certificate, | ||
| 98 | + self._instance._client._client_certificate, | ||
| 99 | + self._instance._client._client_key, | ||
| 100 | + self._interceptors, | ||
| 101 | + ) | ||
| 95 | 102 | self._spanner_api = SpannerClient( | |
| 96 | 103 | client_info=client_info, | |
| 97 | 104 | transport=transport, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -60,7 +60,14 @@ | |||
| 60 | 60 | EXPERIMENTAL_HOST = os.getenv(USE_EXPERIMENTAL_HOST_ENVVAR) | |
| 61 | 61 | USE_EXPERIMENTAL_HOST = EXPERIMENTAL_HOST is not None | |
| 62 | 62 | ||
| 63 | - EXPERIMENTAL_HOST_PROJECT = "default" | ||
| 63 | + CA_CERTIFICATE_ENVVAR = "CA_CERTIFICATE" | ||
| 64 | + CA_CERTIFICATE = os.getenv(CA_CERTIFICATE_ENVVAR) | ||
| 65 | + CLIENT_CERTIFICATE_ENVVAR = "CLIENT_CERTIFICATE" | ||
| 66 | + CLIENT_CERTIFICATE = os.getenv(CLIENT_CERTIFICATE_ENVVAR) | ||
| 67 | + CLIENT_KEY_ENVVAR = "CLIENT_KEY" | ||
| 68 | + CLIENT_KEY = os.getenv(CLIENT_KEY_ENVVAR) | ||
| 69 | + USE_PLAIN_TEXT = CA_CERTIFICATE is None | ||
| 70 | + | ||
| 64 | 71 | EXPERIMENTAL_HOST_INSTANCE = "default" | |
| 65 | 72 | ||
| 66 | 73 | DDL_STATEMENTS = ( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -115,7 +115,10 @@ def spanner_client(): | |||
| 115 | 115 | ||
| 116 | 116 | credentials = AnonymousCredentials() | |
| 117 | 117 | return spanner_v1.Client( | |
| 118 | - project=_helpers.EXPERIMENTAL_HOST_PROJECT, | ||
| 118 | + use_plain_text=_helpers.USE_PLAIN_TEXT, | ||
| 119 | + ca_certificate=_helpers.CA_CERTIFICATE, | ||
| 120 | + client_certificate=_helpers.CLIENT_CERTIFICATE, | ||
| 121 | + client_key=_helpers.CLIENT_KEY, | ||
| 119 | 122 | credentials=credentials, | |
| 120 | 123 | experimental_host=_helpers.EXPERIMENTAL_HOST, | |
| 121 | 124 | ) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1442,6 +1442,10 @@ def test_user_agent(self, shared_instance, dbapi_database): | |||
| 1442 | 1442 | experimental_host=_helpers.EXPERIMENTAL_HOST | |
| 1443 | 1443 | if _helpers.USE_EXPERIMENTAL_HOST | |
| 1444 | 1444 | else None, | |
| 1445 | + use_plain_text=_helpers.USE_PLAIN_TEXT, | ||
| 1446 | + ca_certificate=_helpers.CA_CERTIFICATE, | ||
| 1447 | + client_certificate=_helpers.CLIENT_CERTIFICATE, | ||
| 1448 | + client_key=_helpers.CLIENT_KEY, | ||
| 1445 | 1449 | ) | |
| 1446 | 1450 | assert ( | |
| 1447 | 1451 | conn.instance._client._client_info.user_agent | |
| Back | FazBrowse Home | New Git URL |
0 commit comments