Channelz Python wrapper implementation

* Expose the C-Core API in Cython layer
* Handle the object translation
* Create a separate package for Channelz specifically
* Handle nullptr and raise exception if seen one
* Translate C++ Channelz unit tests
* Adding 5 more invalid query unit tests

Adding peripheral utility for grpcio-channelz package
* Add to `pylint_code.sh`
* Add to Python build script
* Add to artifact build script
* Add to Bazel
* Add to Sphinx module list
pull/17266/head
Lidi Zheng 6 years ago
parent b32d8f5d4d
commit 43599facf4
  1. 3
      doc/python/sphinx/conf.py
  2. 12
      doc/python/sphinx/grpc_channelz.rst
  3. 1
      doc/python/sphinx/index.rst
  4. 7
      src/proto/grpc/channelz/BUILD
  5. 1
      src/proto/grpc/health/v1/BUILD
  6. 1
      src/python/grpcio/grpc/_cython/BUILD.bazel
  7. 56
      src/python/grpcio/grpc/_cython/_cygrpc/channelz.pyx.pxi
  8. 10
      src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
  9. 1
      src/python/grpcio/grpc/_cython/cygrpc.pyx
  10. 4
      src/python/grpcio/grpc/_server.py
  11. 6
      src/python/grpcio_channelz/.gitignore
  12. 3
      src/python/grpcio_channelz/MANIFEST.in
  13. 9
      src/python/grpcio_channelz/README.rst
  14. 63
      src/python/grpcio_channelz/channelz_commands.py
  15. 13
      src/python/grpcio_channelz/grpc_channelz/__init__.py
  16. 38
      src/python/grpcio_channelz/grpc_channelz/v1/BUILD.bazel
  17. 13
      src/python/grpcio_channelz/grpc_channelz/v1/__init__.py
  18. 114
      src/python/grpcio_channelz/grpc_channelz/v1/channelz.py
  19. 17
      src/python/grpcio_channelz/grpc_version.py
  20. 96
      src/python/grpcio_channelz/setup.py
  21. 1
      src/python/grpcio_tests/setup.py
  22. 15
      src/python/grpcio_tests/tests/channelz/BUILD.bazel
  23. 13
      src/python/grpcio_tests/tests/channelz/__init__.py
  24. 476
      src/python/grpcio_tests/tests/channelz/_channelz_servicer_test.py
  25. 1
      src/python/grpcio_tests/tests/tests.json
  26. 19
      templates/src/python/grpcio_channelz/grpc_version.py.template
  27. 1
      tools/distrib/pylint_code.sh
  28. 5
      tools/run_tests/artifacts/build_artifact_python.sh
  29. 5
      tools/run_tests/helper_scripts/build_python.sh

@ -19,6 +19,7 @@ import sys
PYTHON_FOLDER = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'..', '..', '..', 'src', 'python')
sys.path.insert(0, os.path.join(PYTHON_FOLDER, 'grpcio'))
sys.path.insert(0, os.path.join(PYTHON_FOLDER, 'grpcio_channelz'))
sys.path.insert(0, os.path.join(PYTHON_FOLDER, 'grpcio_health_checking'))
sys.path.insert(0, os.path.join(PYTHON_FOLDER, 'grpcio_reflection'))
sys.path.insert(0, os.path.join(PYTHON_FOLDER, 'grpcio_testing'))
@ -63,6 +64,8 @@ autodoc_default_options = {
autodoc_mock_imports = [
'grpc._cython',
'grpc_channelz.v1.channelz_pb2',
'grpc_channelz.v1.channelz_pb2_grpc',
'grpc_health.v1.health_pb2',
'grpc_health.v1.health_pb2_grpc',
'grpc_reflection.v1alpha.reflection_pb2',

@ -0,0 +1,12 @@
gRPC Channelz
====================
What is gRPC Channelz?
---------------------------------------------
Design Document `gRPC Channelz <https://github.com/grpc/proposal/blob/master/A14-channelz.md>`_
Module Contents
---------------
.. automodule:: grpc_channelz.v1.channelz

@ -10,6 +10,7 @@ API Reference
:caption: Contents:
grpc
grpc_channelz
grpc_health_checking
grpc_reflection
grpc_testing

@ -24,3 +24,10 @@ grpc_proto_library(
has_services = True,
well_known_protos = True,
)
filegroup(
name = "channelz_proto_file",
srcs = [
"channelz.proto",
],
)

@ -29,4 +29,3 @@ filegroup(
"health.proto",
],
)

@ -12,6 +12,7 @@ pyx_library(
"_cygrpc/grpc_string.pyx.pxi",
"_cygrpc/arguments.pyx.pxi",
"_cygrpc/call.pyx.pxi",
"_cygrpc/channelz.pyx.pxi",
"_cygrpc/channel.pyx.pxi",
"_cygrpc/credentials.pyx.pxi",
"_cygrpc/completion_queue.pyx.pxi",

@ -0,0 +1,56 @@
# Copyright 2018 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 channelz_get_top_channels(start_channel_id):
cdef char *c_returned_str = grpc_channelz_get_top_channels(start_channel_id)
if c_returned_str == NULL:
raise ValueError('Failed to get top channels, please ensure your start_channel_id==%s is valid' % start_channel_id)
return c_returned_str
def channelz_get_servers(start_server_id):
cdef char *c_returned_str = grpc_channelz_get_servers(start_server_id)
if c_returned_str == NULL:
raise ValueError('Failed to get servers, please ensure your start_server_id==%s is valid' % start_server_id)
return c_returned_str
def channelz_get_server(server_id):
cdef char *c_returned_str = grpc_channelz_get_server(server_id)
if c_returned_str == NULL:
raise ValueError('Failed to get the server, please ensure your server_id==%s is valid' % server_id)
return c_returned_str
def channelz_get_server_sockets(server_id, start_socket_id):
cdef char *c_returned_str = grpc_channelz_get_server_sockets(server_id, start_socket_id)
if c_returned_str == NULL:
raise ValueError('Failed to get server sockets, please ensure your server_id==%s and start_socket_id==%s is valid' % (server_id, start_socket_id))
return c_returned_str
def channelz_get_channel(channel_id):
cdef char *c_returned_str = grpc_channelz_get_channel(channel_id)
if c_returned_str == NULL:
raise ValueError('Failed to get the channel, please ensure your channel_id==%s is valid' % (channel_id))
return c_returned_str
def channelz_get_subchannel(subchannel_id):
cdef char *c_returned_str = grpc_channelz_get_subchannel(subchannel_id)
if c_returned_str == NULL:
raise ValueError('Failed to get the subchannel, please ensure your subchannel_id==%s is valid' % (subchannel_id))
return c_returned_str
def channelz_get_socket(socket_id):
cdef char *c_returned_str = grpc_channelz_get_socket(socket_id)
if c_returned_str == NULL:
raise ValueError('Failed to get the socket, please ensure your socket_id==%s is valid' % (socket_id))
return c_returned_str

@ -13,6 +13,7 @@
# limitations under the License.
cimport libc.time
from libc.stdint cimport intptr_t
# Typedef types with approximately the same semantics to provide their names to
@ -391,6 +392,15 @@ cdef extern from "grpc/grpc.h":
void grpc_server_cancel_all_calls(grpc_server *server) nogil
void grpc_server_destroy(grpc_server *server) nogil
char* grpc_channelz_get_top_channels(intptr_t start_channel_id)
char* grpc_channelz_get_servers(intptr_t start_server_id)
char* grpc_channelz_get_server(intptr_t server_id)
char* grpc_channelz_get_server_sockets(intptr_t server_id,
intptr_t start_socket_id)
char* grpc_channelz_get_channel(intptr_t channel_id)
char* grpc_channelz_get_subchannel(intptr_t subchannel_id)
char* grpc_channelz_get_socket(intptr_t socket_id)
cdef extern from "grpc/grpc_security.h":

@ -35,6 +35,7 @@ include "_cygrpc/server.pyx.pxi"
include "_cygrpc/tag.pyx.pxi"
include "_cygrpc/time.pyx.pxi"
include "_cygrpc/_hooks.pyx.pxi"
include "_cygrpc/channelz.pyx.pxi"
include "_cygrpc/grpc_gevent.pyx.pxi"

@ -828,7 +828,9 @@ class _Server(grpc.Server):
return _stop(self._state, grace)
def __del__(self):
_stop(self._state, None)
if hasattr(self, '_state'):
_stop(self._state, None)
del self._state
def create_server(thread_pool, generic_rpc_handlers, interceptors, options,

@ -0,0 +1,6 @@
*.proto
*_pb2.py
*_pb2_grpc.py
build/
grpcio_channelz.egg-info/
dist/

@ -0,0 +1,3 @@
include grpc_version.py
recursive-include grpc_channelz *.py
global-exclude *.pyc

@ -0,0 +1,9 @@
gRPC Python Channelz package
==============================
Channelz is a live debug tool in gRPC Python.
Dependencies
------------
Depends on the `grpcio` package, available from PyPI via `pip install grpcio`.

@ -0,0 +1,63 @@
# Copyright 2018 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.
"""Provides distutils command classes for the GRPC Python setup process."""
import os
import shutil
import setuptools
ROOT_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
CHANNELZ_PROTO = os.path.join(ROOT_DIR,
'../../proto/grpc/channelz/channelz.proto')
class CopyProtoModules(setuptools.Command):
"""Command to copy proto modules from grpc/src/proto."""
description = ''
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
if os.path.isfile(CHANNELZ_PROTO):
shutil.copyfile(CHANNELZ_PROTO,
os.path.join(ROOT_DIR,
'grpc_channelz/v1/channelz.proto'))
class BuildPackageProtos(setuptools.Command):
"""Command to generate project *_pb2.py modules from proto files."""
description = 'build grpc protobuf modules'
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
# due to limitations of the proto generator, we require that only *one*
# directory is provided as an 'include' directory. We assume it's the '' key
# to `self.distribution.package_dir` (and get a key error if it's not
# there).
from grpc_tools import command
command.build_package_protos(self.distribution.package_dir[''])

@ -0,0 +1,13 @@
# Copyright 2018 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.

@ -0,0 +1,38 @@
load("@grpc_python_dependencies//:requirements.bzl", "requirement")
load("@org_pubref_rules_protobuf//python:rules.bzl", "py_proto_library")
package(default_visibility = ["//visibility:public"])
genrule(
name = "mv_channelz_proto",
srcs = [
"//src/proto/grpc/channelz:channelz_proto_file",
],
outs = ["channelz.proto",],
cmd = "cp $< $@",
)
py_proto_library(
name = "py_channelz_proto",
protos = ["mv_channelz_proto",],
imports = [
"external/com_google_protobuf/src/",
],
inputs = [
"@com_google_protobuf//:well_known_protos",
],
with_grpc = True,
deps = [
requirement('protobuf'),
],
)
py_library(
name = "grpc_channelz",
srcs = ["channelz.py",],
deps = [
":py_channelz_proto",
"//src/python/grpcio/grpc:grpcio",
],
imports=["../../",],
)

@ -0,0 +1,13 @@
# Copyright 2018 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.

@ -0,0 +1,114 @@
# Copyright 2018 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."""
import grpc
from grpc._cython import cygrpc
import grpc_channelz.v1.channelz_pb2 as _channelz_pb2
import grpc_channelz.v1.channelz_pb2_grpc as _channelz_pb2_grpc
from google.protobuf import json_format
class ChannelzServicer(_channelz_pb2_grpc.ChannelzServicer):
"""Servicer handling RPCs for service statuses."""
# pylint: disable=no-self-use
def GetTopChannels(self, request, context):
try:
return json_format.Parse(
cygrpc.channelz_get_top_channels(request.start_channel_id),
_channelz_pb2.GetTopChannelsResponse(),
)
except ValueError as e:
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
context.set_details(str(e))
# pylint: disable=no-self-use
def GetServers(self, request, context):
try:
return json_format.Parse(
cygrpc.channelz_get_servers(request.start_server_id),
_channelz_pb2.GetServersResponse(),
)
except ValueError as e:
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
context.set_details(str(e))
# pylint: disable=no-self-use
def GetServer(self, request, context):
try:
return json_format.Parse(
cygrpc.channelz_get_server(request.server_id),
_channelz_pb2.GetServerResponse(),
)
except ValueError as e:
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
context.set_details(str(e))
# pylint: disable=no-self-use
def GetServerSockets(self, request, context):
try:
return json_format.Parse(
cygrpc.channelz_get_server_sockets(request.server_id,
request.start_socket_id),
_channelz_pb2.GetServerSocketsResponse(),
)
except ValueError as e:
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
context.set_details(str(e))
# pylint: disable=no-self-use
def GetChannel(self, request, context):
try:
return json_format.Parse(
cygrpc.channelz_get_channel(request.channel_id),
_channelz_pb2.GetChannelResponse(),
)
except ValueError as e:
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
context.set_details(str(e))
# pylint: disable=no-self-use
def GetSubchannel(self, request, context):
try:
return json_format.Parse(
cygrpc.channelz_get_subchannel(request.subchannel_id),
_channelz_pb2.GetSubchannelResponse(),
)
except ValueError as e:
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
context.set_details(str(e))
# pylint: disable=no-self-use
def GetSocket(self, request, context):
try:
return json_format.Parse(
cygrpc.channelz_get_socket(request.socket_id),
_channelz_pb2.GetSocketResponse(),
)
except ValueError as e:
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
context.set_details(str(e))
def enable_channelz(server):
"""Enables Channelz on a server.
Args:
server: grpc.Server to which Channelz service will be added.
"""
_channelz_pb2_grpc.add_ChannelzServicer_to_server(ChannelzServicer(),
server)

@ -0,0 +1,17 @@
# Copyright 2018 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_channelz/grpc_version.py.template`!!!
VERSION = '1.18.0.dev0'

@ -0,0 +1,96 @@
# Copyright 2018 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 the GRPC Python package's Channelz."""
import os
import sys
import setuptools
# 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
class _NoOpCommand(setuptools.Command):
"""No-op command."""
description = ''
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
pass
CLASSIFIERS = [
'Development Status :: 5 - Production/Stable',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'License :: OSI Approved :: Apache Software License',
]
PACKAGE_DIRECTORIES = {
'': '.',
}
INSTALL_REQUIRES = (
'protobuf>=3.6.0',
'grpcio>={version}'.format(version=grpc_version.VERSION),
)
try:
import channelz_commands as _channelz_commands
# we are in the build environment, otherwise the above import fails
SETUP_REQUIRES = (
'grpcio-tools=={version}'.format(version=grpc_version.VERSION),)
COMMAND_CLASS = {
# Run preprocess from the repository *before* doing any packaging!
'preprocess': _channelz_commands.CopyProtoModules,
'build_package_protos': _channelz_commands.BuildPackageProtos,
}
except ImportError:
SETUP_REQUIRES = ()
COMMAND_CLASS = {
# wire up commands to no-op not to break the external dependencies
'preprocess': _NoOpCommand,
'build_package_protos': _NoOpCommand,
}
setuptools.setup(
name='grpcio-channelz',
version=grpc_version.VERSION,
license='Apache License 2.0',
description='Channel Level Live Debug Information Service for gRPC',
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,
cmdclass=COMMAND_CLASS)

@ -39,6 +39,7 @@ PACKAGE_DIRECTORIES = {
INSTALL_REQUIRES = (
'coverage>=4.0', 'enum34>=1.0.4',
'grpcio>={version}'.format(version=grpc_version.VERSION),
'grpcio-channelz>={version}'.format(version=grpc_version.VERSION),
'grpcio-tools>={version}'.format(version=grpc_version.VERSION),
'grpcio-health-checking>={version}'.format(version=grpc_version.VERSION),
'oauth2client>=1.4.7', 'protobuf>=3.6.0', 'six>=1.10', 'google-auth>=1.0.0',

@ -0,0 +1,15 @@
package(default_visibility = ["//visibility:public"])
py_test(
name = "channelz_servicer_test",
srcs = ["_channelz_servicer_test.py"],
main = "_channelz_servicer_test.py",
size = "small",
deps = [
"//src/python/grpcio/grpc:grpcio",
"//src/python/grpcio_channelz/grpc_channelz/v1:grpc_channelz",
"//src/python/grpcio_tests/tests/unit:test_common",
"//src/python/grpcio_tests/tests/unit/framework/common:common",
],
imports = ["../../",],
)

@ -0,0 +1,13 @@
# Copyright 2018 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.

@ -0,0 +1,476 @@
# Copyright 2018 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.
"""Tests of grpc_channelz.v1.channelz."""
import unittest
from concurrent import futures
import grpc
from grpc_channelz.v1 import channelz
from grpc_channelz.v1 import channelz_pb2
from grpc_channelz.v1 import channelz_pb2_grpc
from tests.unit import test_common
from tests.unit.framework.common import test_constants
_SUCCESSFUL_UNARY_UNARY = '/test/SuccessfulUnaryUnary'
_FAILED_UNARY_UNARY = '/test/FailedUnaryUnary'
_SUCCESSFUL_STREAM_STREAM = '/test/SuccessfulStreamStream'
_REQUEST = b'\x00\x00\x00'
_RESPONSE = b'\x01\x01\x01'
_DISABLE_REUSE_PORT = (('grpc.so_reuseport', 0),)
_ENABLE_CHANNELZ = (('grpc.enable_channelz', 1),)
_DISABLE_CHANNELZ = (('grpc.enable_channelz', 0),)
def _successful_unary_unary(request, servicer_context):
return _RESPONSE
def _failed_unary_unary(request, servicer_context):
servicer_context.set_code(grpc.StatusCode.INTERNAL)
servicer_context.set_details("Channelz Test Intended Failure")
def _successful_stream_stream(request_iterator, servicer_context):
for _ in request_iterator:
yield _RESPONSE
class _GenericHandler(grpc.GenericRpcHandler):
def service(self, handler_call_details):
if handler_call_details.method == _SUCCESSFUL_UNARY_UNARY:
return grpc.unary_unary_rpc_method_handler(_successful_unary_unary)
elif handler_call_details.method == _FAILED_UNARY_UNARY:
return grpc.unary_unary_rpc_method_handler(_failed_unary_unary)
elif handler_call_details.method == _SUCCESSFUL_STREAM_STREAM:
return grpc.stream_stream_rpc_method_handler(
_successful_stream_stream)
else:
return None
class _ChannelServerPair(object):
def __init__(self):
# Server will enable channelz service
# Bind as attribute to make it gc properly
self._server = grpc.server(
futures.ThreadPoolExecutor(max_workers=3),
options=_DISABLE_REUSE_PORT + _ENABLE_CHANNELZ)
port = self._server.add_insecure_port('[::]:0')
self._server.add_generic_rpc_handlers((_GenericHandler(),))
self._server.start()
# Channel will enable channelz service...
self.channel = grpc.insecure_channel('localhost:%d' % port,
_ENABLE_CHANNELZ)
def __del__(self):
self._server.__del__()
self.channel.close()
def _generate_channel_server_pairs(n):
return [_ChannelServerPair() for i in range(n)]
def _clean_channel_server_pairs(pairs):
for pair in pairs:
pair.__del__()
class ChannelzServicerTest(unittest.TestCase):
def _send_successful_unary_unary(self, idx):
_, r = self._pairs[idx].channel.unary_unary(
_SUCCESSFUL_UNARY_UNARY).with_call(_REQUEST)
self.assertEqual(r.code(), grpc.StatusCode.OK)
def _send_failed_unary_unary(self, idx):
try:
self._pairs[idx].channel.unary_unary(_FAILED_UNARY_UNARY).with_call(
_REQUEST)
except grpc.RpcError:
return
else:
self.fail("This call supposed to fail")
def _send_successful_stream_stream(self, idx):
response_iterator = self._pairs[idx].channel.stream_stream(
_SUCCESSFUL_STREAM_STREAM).__call__(
iter([_REQUEST] * test_constants.STREAM_LENGTH))
cnt = 0
for _ in response_iterator:
cnt += 1
self.assertEqual(cnt, test_constants.STREAM_LENGTH)
def _get_channel_id(self, idx):
"""Channel id may not be consecutive"""
resp = self._channelz_stub.GetTopChannels(
channelz_pb2.GetTopChannelsRequest(start_channel_id=0))
self.assertGreater(len(resp.channel), idx)
return resp.channel[idx].ref.channel_id
def setUp(self):
# This server is for Channelz info fetching only
# It self should not enable Channelz
self._server = grpc.server(
futures.ThreadPoolExecutor(max_workers=3),
options=_DISABLE_REUSE_PORT + _DISABLE_CHANNELZ)
port = self._server.add_insecure_port('[::]:0')
channelz_pb2_grpc.add_ChannelzServicer_to_server(
channelz.ChannelzServicer(),
self._server,
)
self._server.start()
# This channel is used to fetch Channelz info only
# Channelz should not be enabled
self._channel = grpc.insecure_channel('localhost:%d' % port,
_DISABLE_CHANNELZ)
self._channelz_stub = channelz_pb2_grpc.ChannelzStub(self._channel)
def tearDown(self):
self._server.__del__()
self._channel.close()
# _pairs may not exist, if the test crashed during setup
if hasattr(self, '_pairs'):
_clean_channel_server_pairs(self._pairs)
def test_get_top_channels_basic(self):
self._pairs = _generate_channel_server_pairs(1)
resp = self._channelz_stub.GetTopChannels(
channelz_pb2.GetTopChannelsRequest(start_channel_id=0))
self.assertEqual(len(resp.channel), 1)
self.assertEqual(resp.end, True)
def test_get_top_channels_high_start_id(self):
self._pairs = _generate_channel_server_pairs(1)
resp = self._channelz_stub.GetTopChannels(
channelz_pb2.GetTopChannelsRequest(start_channel_id=10000))
self.assertEqual(len(resp.channel), 0)
self.assertEqual(resp.end, True)
def test_successful_request(self):
self._pairs = _generate_channel_server_pairs(1)
self._send_successful_unary_unary(0)
resp = self._channelz_stub.GetChannel(
channelz_pb2.GetChannelRequest(channel_id=self._get_channel_id(0)))
self.assertEqual(resp.channel.data.calls_started, 1)
self.assertEqual(resp.channel.data.calls_succeeded, 1)
self.assertEqual(resp.channel.data.calls_failed, 0)
def test_failed_request(self):
self._pairs = _generate_channel_server_pairs(1)
self._send_failed_unary_unary(0)
resp = self._channelz_stub.GetChannel(
channelz_pb2.GetChannelRequest(channel_id=self._get_channel_id(0)))
self.assertEqual(resp.channel.data.calls_started, 1)
self.assertEqual(resp.channel.data.calls_succeeded, 0)
self.assertEqual(resp.channel.data.calls_failed, 1)
def test_many_requests(self):
self._pairs = _generate_channel_server_pairs(1)
k_success = 7
k_failed = 9
for i in range(k_success):
self._send_successful_unary_unary(0)
for i in range(k_failed):
self._send_failed_unary_unary(0)
resp = self._channelz_stub.GetChannel(
channelz_pb2.GetChannelRequest(channel_id=self._get_channel_id(0)))
self.assertEqual(resp.channel.data.calls_started, k_success + k_failed)
self.assertEqual(resp.channel.data.calls_succeeded, k_success)
self.assertEqual(resp.channel.data.calls_failed, k_failed)
def test_many_channel(self):
k_channels = 4
self._pairs = _generate_channel_server_pairs(k_channels)
resp = self._channelz_stub.GetTopChannels(
channelz_pb2.GetTopChannelsRequest(start_channel_id=0))
self.assertEqual(len(resp.channel), k_channels)
def test_many_requests_many_channel(self):
k_channels = 4
self._pairs = _generate_channel_server_pairs(k_channels)
k_success = 11
k_failed = 13
for i in range(k_success):
self._send_successful_unary_unary(0)
self._send_successful_unary_unary(2)
for i in range(k_failed):
self._send_failed_unary_unary(1)
self._send_failed_unary_unary(2)
# The first channel saw only successes
resp = self._channelz_stub.GetChannel(
channelz_pb2.GetChannelRequest(channel_id=self._get_channel_id(0)))
self.assertEqual(resp.channel.data.calls_started, k_success)
self.assertEqual(resp.channel.data.calls_succeeded, k_success)
self.assertEqual(resp.channel.data.calls_failed, 0)
# The second channel saw only failures
resp = self._channelz_stub.GetChannel(
channelz_pb2.GetChannelRequest(channel_id=self._get_channel_id(1)))
self.assertEqual(resp.channel.data.calls_started, k_failed)
self.assertEqual(resp.channel.data.calls_succeeded, 0)
self.assertEqual(resp.channel.data.calls_failed, k_failed)
# The third channel saw both successes and failures
resp = self._channelz_stub.GetChannel(
channelz_pb2.GetChannelRequest(channel_id=self._get_channel_id(2)))
self.assertEqual(resp.channel.data.calls_started, k_success + k_failed)
self.assertEqual(resp.channel.data.calls_succeeded, k_success)
self.assertEqual(resp.channel.data.calls_failed, k_failed)
# The fourth channel saw nothing
resp = self._channelz_stub.GetChannel(
channelz_pb2.GetChannelRequest(channel_id=self._get_channel_id(3)))
self.assertEqual(resp.channel.data.calls_started, 0)
self.assertEqual(resp.channel.data.calls_succeeded, 0)
self.assertEqual(resp.channel.data.calls_failed, 0)
def test_many_subchannels(self):
k_channels = 4
self._pairs = _generate_channel_server_pairs(k_channels)
k_success = 17
k_failed = 19
for i in range(k_success):
self._send_successful_unary_unary(0)
self._send_successful_unary_unary(2)
for i in range(k_failed):
self._send_failed_unary_unary(1)
self._send_failed_unary_unary(2)
gtc_resp = self._channelz_stub.GetTopChannels(
channelz_pb2.GetTopChannelsRequest(start_channel_id=0))
self.assertEqual(len(gtc_resp.channel), k_channels)
for i in range(k_channels):
# If no call performed in the channel, there shouldn't be any subchannel
if gtc_resp.channel[i].data.calls_started == 0:
self.assertEqual(len(gtc_resp.channel[i].subchannel_ref), 0)
continue
# Otherwise, the subchannel should exist
self.assertGreater(len(gtc_resp.channel[i].subchannel_ref), 0)
gsc_resp = self._channelz_stub.GetSubchannel(
channelz_pb2.GetSubchannelRequest(
subchannel_id=gtc_resp.channel[i].subchannel_ref[
0].subchannel_id))
self.assertEqual(gtc_resp.channel[i].data.calls_started,
gsc_resp.subchannel.data.calls_started)
self.assertEqual(gtc_resp.channel[i].data.calls_succeeded,
gsc_resp.subchannel.data.calls_succeeded)
self.assertEqual(gtc_resp.channel[i].data.calls_failed,
gsc_resp.subchannel.data.calls_failed)
def test_server_basic(self):
self._pairs = _generate_channel_server_pairs(1)
resp = self._channelz_stub.GetServers(
channelz_pb2.GetServersRequest(start_server_id=0))
self.assertEqual(len(resp.server), 1)
def test_get_one_server(self):
self._pairs = _generate_channel_server_pairs(1)
gss_resp = self._channelz_stub.GetServers(
channelz_pb2.GetServersRequest(start_server_id=0))
self.assertEqual(len(gss_resp.server), 1)
gs_resp = self._channelz_stub.GetServer(
channelz_pb2.GetServerRequest(
server_id=gss_resp.server[0].ref.server_id))
self.assertEqual(gss_resp.server[0].ref.server_id,
gs_resp.server.ref.server_id)
def test_server_call(self):
self._pairs = _generate_channel_server_pairs(1)
k_success = 23
k_failed = 29
for i in range(k_success):
self._send_successful_unary_unary(0)
for i in range(k_failed):
self._send_failed_unary_unary(0)
resp = self._channelz_stub.GetServers(
channelz_pb2.GetServersRequest(start_server_id=0))
self.assertEqual(len(resp.server), 1)
self.assertEqual(resp.server[0].data.calls_started,
k_success + k_failed)
self.assertEqual(resp.server[0].data.calls_succeeded, k_success)
self.assertEqual(resp.server[0].data.calls_failed, k_failed)
def test_many_subchannels_and_sockets(self):
k_channels = 4
self._pairs = _generate_channel_server_pairs(k_channels)
k_success = 3
k_failed = 5
for i in range(k_success):
self._send_successful_unary_unary(0)
self._send_successful_unary_unary(2)
for i in range(k_failed):
self._send_failed_unary_unary(1)
self._send_failed_unary_unary(2)
gtc_resp = self._channelz_stub.GetTopChannels(
channelz_pb2.GetTopChannelsRequest(start_channel_id=0))
self.assertEqual(len(gtc_resp.channel), k_channels)
for i in range(k_channels):
# If no call performed in the channel, there shouldn't be any subchannel
if gtc_resp.channel[i].data.calls_started == 0:
self.assertEqual(len(gtc_resp.channel[i].subchannel_ref), 0)
continue
# Otherwise, the subchannel should exist
self.assertGreater(len(gtc_resp.channel[i].subchannel_ref), 0)
gsc_resp = self._channelz_stub.GetSubchannel(
channelz_pb2.GetSubchannelRequest(
subchannel_id=gtc_resp.channel[i].subchannel_ref[
0].subchannel_id))
self.assertEqual(len(gsc_resp.subchannel.socket_ref), 1)
gs_resp = self._channelz_stub.GetSocket(
channelz_pb2.GetSocketRequest(
socket_id=gsc_resp.subchannel.socket_ref[0].socket_id))
self.assertEqual(gsc_resp.subchannel.data.calls_started,
gs_resp.socket.data.streams_started)
self.assertEqual(gsc_resp.subchannel.data.calls_started,
gs_resp.socket.data.streams_succeeded)
# Calls started == messages sent, only valid for unary calls
self.assertEqual(gsc_resp.subchannel.data.calls_started,
gs_resp.socket.data.messages_sent)
# Only receive responses when the RPC was successful
self.assertEqual(gsc_resp.subchannel.data.calls_succeeded,
gs_resp.socket.data.messages_received)
def test_streaming_rpc(self):
self._pairs = _generate_channel_server_pairs(1)
# In C++, the argument for _send_successful_stream_stream is message length.
# Here the argument is still channel idx, to be consistent with the other two.
self._send_successful_stream_stream(0)
gc_resp = self._channelz_stub.GetChannel(
channelz_pb2.GetChannelRequest(channel_id=self._get_channel_id(0)))
self.assertEqual(gc_resp.channel.data.calls_started, 1)
self.assertEqual(gc_resp.channel.data.calls_succeeded, 1)
self.assertEqual(gc_resp.channel.data.calls_failed, 0)
# Subchannel exists
self.assertGreater(len(gc_resp.channel.subchannel_ref), 0)
gsc_resp = self._channelz_stub.GetSubchannel(
channelz_pb2.GetSubchannelRequest(
subchannel_id=gc_resp.channel.subchannel_ref[0].subchannel_id))
self.assertEqual(gsc_resp.subchannel.data.calls_started, 1)
self.assertEqual(gsc_resp.subchannel.data.calls_succeeded, 1)
self.assertEqual(gsc_resp.subchannel.data.calls_failed, 0)
# Socket exists
self.assertEqual(len(gsc_resp.subchannel.socket_ref), 1)
gs_resp = self._channelz_stub.GetSocket(
channelz_pb2.GetSocketRequest(
socket_id=gsc_resp.subchannel.socket_ref[0].socket_id))
self.assertEqual(gs_resp.socket.data.streams_started, 1)
self.assertEqual(gs_resp.socket.data.streams_succeeded, 1)
self.assertEqual(gs_resp.socket.data.streams_failed, 0)
self.assertEqual(gs_resp.socket.data.messages_sent,
test_constants.STREAM_LENGTH)
self.assertEqual(gs_resp.socket.data.messages_received,
test_constants.STREAM_LENGTH)
def test_server_sockets(self):
self._pairs = _generate_channel_server_pairs(1)
self._send_successful_unary_unary(0)
self._send_failed_unary_unary(0)
gs_resp = self._channelz_stub.GetServers(
channelz_pb2.GetServersRequest(start_server_id=0))
self.assertEqual(len(gs_resp.server), 1)
self.assertEqual(gs_resp.server[0].data.calls_started, 2)
self.assertEqual(gs_resp.server[0].data.calls_succeeded, 1)
self.assertEqual(gs_resp.server[0].data.calls_failed, 1)
gss_resp = self._channelz_stub.GetServerSockets(
channelz_pb2.GetServerSocketsRequest(
server_id=gs_resp.server[0].ref.server_id, start_socket_id=0))
# If the RPC call failed, it will raise a grpc.RpcError
# So, if there is no exception raised, considered pass
def test_server_listen_sockets(self):
self._pairs = _generate_channel_server_pairs(1)
gss_resp = self._channelz_stub.GetServers(
channelz_pb2.GetServersRequest(start_server_id=0))
self.assertEqual(len(gss_resp.server), 1)
self.assertEqual(len(gss_resp.server[0].listen_socket), 1)
gs_resp = self._channelz_stub.GetSocket(
channelz_pb2.GetSocketRequest(
socket_id=gss_resp.server[0].listen_socket[0].socket_id))
# If the RPC call failed, it will raise a grpc.RpcError
# So, if there is no exception raised, considered pass
def test_invalid_query_get_server(self):
try:
self._channelz_stub.GetServer(
channelz_pb2.GetServerRequest(server_id=10000))
except BaseException as e:
self.assertIn('StatusCode.INVALID_ARGUMENT', str(e))
else:
self.fail('Invalid query not detected')
def test_invalid_query_get_channel(self):
try:
self._channelz_stub.GetChannel(
channelz_pb2.GetChannelRequest(channel_id=10000))
except BaseException as e:
self.assertIn('StatusCode.INVALID_ARGUMENT', str(e))
else:
self.fail('Invalid query not detected')
def test_invalid_query_get_subchannel(self):
try:
self._channelz_stub.GetSubchannel(
channelz_pb2.GetSubchannelRequest(subchannel_id=10000))
except BaseException as e:
self.assertIn('StatusCode.INVALID_ARGUMENT', str(e))
else:
self.fail('Invalid query not detected')
def test_invalid_query_get_socket(self):
try:
self._channelz_stub.GetSocket(
channelz_pb2.GetSocketRequest(socket_id=10000))
except BaseException as e:
self.assertIn('StatusCode.INVALID_ARGUMENT', str(e))
else:
self.fail('Invalid query not detected')
def test_invalid_query_get_server_sockets(self):
try:
self._channelz_stub.GetServerSockets(
channelz_pb2.GetServerSocketsRequest(
server_id=10000,
start_socket_id=0,
))
except BaseException as e:
self.assertIn('StatusCode.INVALID_ARGUMENT', str(e))
else:
self.fail('Invalid query not detected')
if __name__ == '__main__':
unittest.main(verbosity=2)

@ -1,5 +1,6 @@
[
"_sanity._sanity_test.SanityTest",
"channelz._channelz_servicer_test.ChannelzServicerTest",
"health_check._health_servicer_test.HealthServicerTest",
"interop._insecure_intraop_test.InsecureIntraopTest",
"interop._secure_intraop_test.SecureIntraopTest",

@ -0,0 +1,19 @@
%YAML 1.2
--- |
# Copyright 2018 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_channelz/grpc_version.py.template`!!!
VERSION = '${settings.python_version.pep440()}'

@ -20,6 +20,7 @@ cd "$(dirname "$0")/../.."
DIRS=(
'src/python/grpcio/grpc'
'src/python/grpcio_channelz/grpc_channelz'
'src/python/grpcio_health_checking/grpc_health'
'src/python/grpcio_reflection/grpc_reflection'
'src/python/grpcio_testing/grpc_testing'

@ -108,6 +108,11 @@ then
${SETARCH_CMD} "${PYTHON}" src/python/grpcio_testing/setup.py sdist
cp -r src/python/grpcio_testing/dist/* "$ARTIFACT_DIR"
# Build grpcio_channelz source distribution
${SETARCH_CMD} "${PYTHON}" src/python/grpcio_channelz/setup.py \
preprocess build_package_protos sdist
cp -r src/python/grpcio_channelz/dist/* "$ARTIFACT_DIR"
# Build grpcio_health_checking source distribution
${SETARCH_CMD} "${PYTHON}" src/python/grpcio_health_checking/setup.py \
preprocess build_package_protos sdist

@ -189,6 +189,11 @@ pip_install_dir "$ROOT"
$VENV_PYTHON "$ROOT/tools/distrib/python/make_grpcio_tools.py"
pip_install_dir "$ROOT/tools/distrib/python/grpcio_tools"
# Build/install Chaneelz
$VENV_PYTHON "$ROOT/src/python/grpcio_channelz/setup.py" preprocess
$VENV_PYTHON "$ROOT/src/python/grpcio_channelz/setup.py" build_package_protos
pip_install_dir "$ROOT/src/python/grpcio_channelz"
# Build/install health checking
$VENV_PYTHON "$ROOT/src/python/grpcio_health_checking/setup.py" preprocess
$VENV_PYTHON "$ROOT/src/python/grpcio_health_checking/setup.py" build_package_protos

Loading…
Cancel
Save