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