mirror of https://github.com/grpc/grpc.git
Add CSDS API to Python (#26114)
* Add grpcio-csds pacakge * Remove unused file * Fix the proto import path issue * Update the CSDS package and xds-protos for PY2 * Make tests happy * Fix Bazel proto dependency * Add Python2 tests for CSDSreviewable/pr26166/r1
parent
ff79a925ed
commit
dc63d6a53e
20 changed files with 460 additions and 22 deletions
@ -0,0 +1,21 @@ |
|||||||
|
# Copyright 2021 The gRPC Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
|
||||||
|
|
||||||
|
def dump_xds_configs(): |
||||||
|
cdef grpc_slice client_config_in_slice |
||||||
|
with nogil: |
||||||
|
client_config_in_slice = grpc_dump_xds_configs() |
||||||
|
cdef bytes result = _slice_bytes(client_config_in_slice) |
||||||
|
return result |
@ -0,0 +1,6 @@ |
|||||||
|
*.proto |
||||||
|
*_pb2.py |
||||||
|
*_pb2_grpc.py |
||||||
|
build/ |
||||||
|
grpcio_channelz.egg-info/ |
||||||
|
dist/ |
@ -0,0 +1,4 @@ |
|||||||
|
include grpc_version.py |
||||||
|
recursive-include grpc_csds *.py |
||||||
|
global-exclude *.pyc |
||||||
|
include LICENSE |
@ -0,0 +1,10 @@ |
|||||||
|
gRPC Python Client Status Discovery Service package |
||||||
|
=================================================== |
||||||
|
|
||||||
|
CSDS is part of the Envoy xDS protocol: |
||||||
|
https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/status/v3/csds.proto. |
||||||
|
It allows the gRPC application to programmatically expose the received traffic |
||||||
|
configuration (xDS resources). Welcome to explore with CLI tool "grpcdebug": |
||||||
|
https://github.com/grpc-ecosystem/grpcdebug. |
||||||
|
|
||||||
|
For any issues or suggestions, please send to https://github.com/grpc/grpc/issues. |
@ -0,0 +1,31 @@ |
|||||||
|
# Copyright 2021 The gRPC Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
|
||||||
|
load("@grpc_python_dependencies//:requirements.bzl", "requirement") |
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"]) |
||||||
|
|
||||||
|
py_library( |
||||||
|
name = "grpc_csds", |
||||||
|
srcs = glob(["*.py"]), |
||||||
|
imports = ["../"], |
||||||
|
deps = [ |
||||||
|
"//src/proto/grpc/testing/xds/v3:base_py_pb2", |
||||||
|
"//src/proto/grpc/testing/xds/v3:config_dump_py_pb2", |
||||||
|
"//src/proto/grpc/testing/xds/v3:csds_py_pb2", |
||||||
|
"//src/proto/grpc/testing/xds/v3:csds_py_pb2_grpc", |
||||||
|
"//src/proto/grpc/testing/xds/v3:percent_py_pb2", |
||||||
|
"//src/python/grpcio/grpc:grpcio", |
||||||
|
], |
||||||
|
) |
@ -0,0 +1,49 @@ |
|||||||
|
# Copyright 2021 The gRPC Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
"""Channelz debug service implementation in gRPC Python.""" |
||||||
|
|
||||||
|
from grpc._cython import cygrpc |
||||||
|
|
||||||
|
from google.protobuf import json_format |
||||||
|
try: |
||||||
|
from envoy.service.status.v3 import csds_pb2, csds_pb2_grpc |
||||||
|
except ImportError: |
||||||
|
from src.proto.grpc.testing.xds.v3 import csds_pb2, csds_pb2_grpc |
||||||
|
|
||||||
|
|
||||||
|
class ClientStatusDiscoveryServiceServicer( |
||||||
|
csds_pb2_grpc.ClientStatusDiscoveryServiceServicer): |
||||||
|
"""CSDS Servicer works for both the sync API and asyncio API.""" |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def FetchClientStatus(request, unused_context): |
||||||
|
client_config = csds_pb2.ClientConfig.FromString( |
||||||
|
cygrpc.dump_xds_configs()) |
||||||
|
response = csds_pb2.ClientStatusResponse() |
||||||
|
response.config.append(client_config) |
||||||
|
return response |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def StreamClientStatus(request_iterator, context): |
||||||
|
for request in request_iterator: |
||||||
|
yield ClientStatusDiscoveryServiceServicer.FetchClientStatus( |
||||||
|
request, context) |
||||||
|
|
||||||
|
|
||||||
|
def add_csds_servicer(server): |
||||||
|
csds_pb2_grpc.add_ClientStatusDiscoveryServiceServicer_to_server( |
||||||
|
ClientStatusDiscoveryServiceServicer(), server) |
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['ClientStatusDiscoveryServiceServicer', 'add_csds_servicer'] |
@ -0,0 +1,17 @@ |
|||||||
|
# Copyright 2021 The gRPC Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
|
||||||
|
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_csds/grpc_version.py.template`!!! |
||||||
|
|
||||||
|
VERSION = '1.38.0.dev0' |
@ -0,0 +1,61 @@ |
|||||||
|
# Copyright 2021 The gRPC Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
"""Setup module for CSDS in gRPC Python.""" |
||||||
|
|
||||||
|
import os |
||||||
|
import sys |
||||||
|
|
||||||
|
import setuptools |
||||||
|
|
||||||
|
_PACKAGE_PATH = os.path.realpath(os.path.dirname(__file__)) |
||||||
|
_README_PATH = os.path.join(_PACKAGE_PATH, 'README.rst') |
||||||
|
|
||||||
|
# Ensure we're in the proper directory whether or not we're being used by pip. |
||||||
|
os.chdir(os.path.dirname(os.path.abspath(__file__))) |
||||||
|
|
||||||
|
# Break import-style to ensure we can actually find our local modules. |
||||||
|
import grpc_version |
||||||
|
|
||||||
|
CLASSIFIERS = [ |
||||||
|
'Development Status :: 5 - Production/Stable', |
||||||
|
'Programming Language :: Python', |
||||||
|
'Programming Language :: Python :: 2', |
||||||
|
'Programming Language :: Python :: 3', |
||||||
|
'License :: OSI Approved :: Apache Software License', |
||||||
|
] |
||||||
|
|
||||||
|
PACKAGE_DIRECTORIES = { |
||||||
|
'': '.', |
||||||
|
} |
||||||
|
|
||||||
|
INSTALL_REQUIRES = ( |
||||||
|
'protobuf>=3.6.0', |
||||||
|
'xds-protos>=0.0.7', |
||||||
|
'grpcio>={version}'.format(version=grpc_version.VERSION), |
||||||
|
) |
||||||
|
SETUP_REQUIRES = INSTALL_REQUIRES |
||||||
|
|
||||||
|
setuptools.setup(name='grpcio-csds', |
||||||
|
version=grpc_version.VERSION, |
||||||
|
license='Apache License 2.0', |
||||||
|
description='xDS configuration dump library', |
||||||
|
long_description=open(_README_PATH, 'r').read(), |
||||||
|
author='The gRPC Authors', |
||||||
|
author_email='grpc-io@googlegroups.com', |
||||||
|
classifiers=CLASSIFIERS, |
||||||
|
url='https://grpc.io', |
||||||
|
package_dir=PACKAGE_DIRECTORIES, |
||||||
|
packages=setuptools.find_packages('.'), |
||||||
|
install_requires=INSTALL_REQUIRES, |
||||||
|
setup_requires=SETUP_REQUIRES) |
@ -0,0 +1,27 @@ |
|||||||
|
# Copyright 2021 The gRPC Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
|
||||||
|
load("//bazel:python_rules.bzl", "py2and3_test") |
||||||
|
|
||||||
|
py2and3_test( |
||||||
|
name = "test_csds", |
||||||
|
size = "small", |
||||||
|
srcs = ["test_csds.py"], |
||||||
|
main = "test_csds.py", |
||||||
|
deps = [ |
||||||
|
"//src/python/grpcio/grpc:grpcio", |
||||||
|
"//src/python/grpcio_csds/grpc_csds", |
||||||
|
"@six", |
||||||
|
], |
||||||
|
) |
@ -0,0 +1,134 @@ |
|||||||
|
# Copyright 2021 The gRPC Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
"""A simple test to ensure that the Python wrapper can get xDS config.""" |
||||||
|
|
||||||
|
import logging |
||||||
|
import os |
||||||
|
import time |
||||||
|
from six.moves import queue |
||||||
|
import unittest |
||||||
|
from concurrent.futures import ThreadPoolExecutor |
||||||
|
|
||||||
|
import grpc |
||||||
|
import grpc_csds |
||||||
|
|
||||||
|
from google.protobuf import json_format |
||||||
|
try: |
||||||
|
from envoy.service.status.v3 import csds_pb2, csds_pb2_grpc |
||||||
|
except ImportError: |
||||||
|
from src.proto.grpc.testing.xds.v3 import csds_pb2, csds_pb2_grpc |
||||||
|
|
||||||
|
_DUMMY_XDS_ADDRESS = 'xds:///foo.bar' |
||||||
|
_DUMMY_BOOTSTRAP_FILE = """ |
||||||
|
{ |
||||||
|
\"xds_servers\": [ |
||||||
|
{ |
||||||
|
\"server_uri\": \"fake:///xds_server\", |
||||||
|
\"channel_creds\": [ |
||||||
|
{ |
||||||
|
\"type\": \"fake\" |
||||||
|
} |
||||||
|
], |
||||||
|
\"server_features\": [\"xds_v3\"] |
||||||
|
} |
||||||
|
], |
||||||
|
\"node\": { |
||||||
|
\"id\": \"python_test_csds\", |
||||||
|
\"cluster\": \"test\", |
||||||
|
\"metadata\": { |
||||||
|
\"foo\": \"bar\" |
||||||
|
}, |
||||||
|
\"locality\": { |
||||||
|
\"region\": \"corp\", |
||||||
|
\"zone\": \"svl\", |
||||||
|
\"sub_zone\": \"mp3\" |
||||||
|
} |
||||||
|
} |
||||||
|
}\ |
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
class TestCsds(unittest.TestCase): |
||||||
|
|
||||||
|
def setUp(self): |
||||||
|
os.environ['GRPC_XDS_BOOTSTRAP_CONFIG'] = _DUMMY_BOOTSTRAP_FILE |
||||||
|
self._server = grpc.server(ThreadPoolExecutor()) |
||||||
|
port = self._server.add_insecure_port('localhost:0') |
||||||
|
grpc_csds.add_csds_servicer(self._server) |
||||||
|
self._server.start() |
||||||
|
|
||||||
|
self._channel = grpc.insecure_channel('localhost:%s' % port) |
||||||
|
self._stub = csds_pb2_grpc.ClientStatusDiscoveryServiceStub( |
||||||
|
self._channel) |
||||||
|
|
||||||
|
def tearDown(self): |
||||||
|
self._channel.close() |
||||||
|
self._server.stop(0) |
||||||
|
os.environ.pop('GRPC_XDS_BOOTSTRAP_CONFIG', None) |
||||||
|
|
||||||
|
def get_xds_config_dump(self): |
||||||
|
return self._stub.FetchClientStatus(csds_pb2.ClientStatusRequest()) |
||||||
|
|
||||||
|
def test_has_node(self): |
||||||
|
resp = self.get_xds_config_dump() |
||||||
|
self.assertEqual(1, len(resp.config)) |
||||||
|
self.assertEqual(4, len(resp.config[0].xds_config)) |
||||||
|
self.assertEqual('python_test_csds', resp.config[0].node.id) |
||||||
|
self.assertEqual('test', resp.config[0].node.cluster) |
||||||
|
|
||||||
|
def test_no_lds_found(self): |
||||||
|
dummy_channel = grpc.insecure_channel(_DUMMY_XDS_ADDRESS) |
||||||
|
|
||||||
|
# Force the XdsClient to initialize and request a resource |
||||||
|
with self.assertRaises(grpc.RpcError) as rpc_error: |
||||||
|
dummy_channel.unary_unary('')(b'', wait_for_ready=False) |
||||||
|
self.assertEqual(grpc.StatusCode.UNAVAILABLE, |
||||||
|
rpc_error.exception.code()) |
||||||
|
|
||||||
|
# The resource request will fail with DOES_NOT_EXIST (after 15s) |
||||||
|
while True: |
||||||
|
resp = self.get_xds_config_dump() |
||||||
|
config = json_format.MessageToDict(resp) |
||||||
|
ok = False |
||||||
|
try: |
||||||
|
for xds_config in config["config"][0]["xdsConfig"]: |
||||||
|
if "listenerConfig" in xds_config: |
||||||
|
listener = xds_config["listenerConfig"][ |
||||||
|
"dynamicListeners"][0] |
||||||
|
if listener['clientStatus'] == 'DOES_NOT_EXIST': |
||||||
|
ok = True |
||||||
|
break |
||||||
|
except KeyError as e: |
||||||
|
logging.debug("Invalid config: %s\n%s: %s", config, type(e), e) |
||||||
|
pass |
||||||
|
if ok: |
||||||
|
break |
||||||
|
time.sleep(1) |
||||||
|
dummy_channel.close() |
||||||
|
|
||||||
|
|
||||||
|
class TestCsdsStream(TestCsds): |
||||||
|
|
||||||
|
def get_xds_config_dump(self): |
||||||
|
if not hasattr(self, 'request_queue'): |
||||||
|
request_queue = queue.Queue() |
||||||
|
response_iterator = self._stub.StreamClientStatus( |
||||||
|
iter(request_queue.get, None)) |
||||||
|
request_queue.put(csds_pb2.ClientStatusRequest()) |
||||||
|
return next(response_iterator) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
logging.basicConfig(level=logging.DEBUG) |
||||||
|
unittest.main(verbosity=2) |
@ -0,0 +1,19 @@ |
|||||||
|
%YAML 1.2 |
||||||
|
--- | |
||||||
|
# Copyright 2021 The gRPC Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
|
||||||
|
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_csds/grpc_version.py.template`!!! |
||||||
|
|
||||||
|
VERSION = '${settings.python_version.pep440()}' |
Loading…
Reference in new issue