aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Lidi Zheng <scallopsky@gmail.com>2018-11-29 16:10:32 -0800
committerGravatar GitHub <noreply@github.com>2018-11-29 16:10:32 -0800
commit7ef8fc826c52b0c4abeaead633d464197fc0bdf8 (patch)
tree86db374a5e189e730fea2fef843b50d37b9846ab
parente91e311a736a94e2f9d7ffdb7de4a5249f2b162f (diff)
parent69b6c047bc767b4d80e7af4d00ccb7c45b683dae (diff)
Merge pull request #17266 from lidizheng/python-channelz
Channelz Python wrapper implementation
-rw-r--r--doc/python/sphinx/conf.py3
-rw-r--r--doc/python/sphinx/grpc_channelz.rst12
-rw-r--r--doc/python/sphinx/index.rst1
-rw-r--r--src/proto/grpc/channelz/BUILD7
-rw-r--r--src/proto/grpc/health/v1/BUILD1
-rw-r--r--src/python/grpcio/grpc/_cython/BUILD.bazel1
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/channelz.pyx.pxi69
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi10
-rw-r--r--src/python/grpcio/grpc/_cython/cygrpc.pyx1
-rw-r--r--src/python/grpcio_channelz/.gitignore6
-rw-r--r--src/python/grpcio_channelz/MANIFEST.in3
-rw-r--r--src/python/grpcio_channelz/README.rst9
-rw-r--r--src/python/grpcio_channelz/channelz_commands.py63
-rw-r--r--src/python/grpcio_channelz/grpc_channelz/__init__.py13
-rw-r--r--src/python/grpcio_channelz/grpc_channelz/v1/BUILD.bazel38
-rw-r--r--src/python/grpcio_channelz/grpc_channelz/v1/__init__.py13
-rw-r--r--src/python/grpcio_channelz/grpc_channelz/v1/channelz.py141
-rw-r--r--src/python/grpcio_channelz/grpc_version.py17
-rw-r--r--src/python/grpcio_channelz/setup.py96
-rw-r--r--src/python/grpcio_tests/commands.py7
-rw-r--r--src/python/grpcio_tests/setup.py1
-rw-r--r--src/python/grpcio_tests/tests/channelz/BUILD.bazel15
-rw-r--r--src/python/grpcio_tests/tests/channelz/__init__.py13
-rw-r--r--src/python/grpcio_tests/tests/channelz/_channelz_servicer_test.py494
-rw-r--r--src/python/grpcio_tests/tests/tests.json1
-rw-r--r--templates/src/python/grpcio_channelz/grpc_version.py.template19
-rwxr-xr-xtools/distrib/pylint_code.sh1
-rwxr-xr-xtools/run_tests/artifacts/build_artifact_python.sh5
-rwxr-xr-xtools/run_tests/helper_scripts/build_python.sh5
29 files changed, 1063 insertions, 2 deletions
diff --git a/doc/python/sphinx/conf.py b/doc/python/sphinx/conf.py
index 1eb3a5de7f..307c3bdaf6 100644
--- a/doc/python/sphinx/conf.py
+++ b/doc/python/sphinx/conf.py
@@ -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',
diff --git a/doc/python/sphinx/grpc_channelz.rst b/doc/python/sphinx/grpc_channelz.rst
new file mode 100644
index 0000000000..f65793a071
--- /dev/null
+++ b/doc/python/sphinx/grpc_channelz.rst
@@ -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
diff --git a/doc/python/sphinx/index.rst b/doc/python/sphinx/index.rst
index 322ca33e15..2f8a47a074 100644
--- a/doc/python/sphinx/index.rst
+++ b/doc/python/sphinx/index.rst
@@ -10,6 +10,7 @@ API Reference
:caption: Contents:
grpc
+ grpc_channelz
grpc_health_checking
grpc_reflection
grpc_testing
diff --git a/src/proto/grpc/channelz/BUILD b/src/proto/grpc/channelz/BUILD
index bdb03d5e2d..b6b485e3e8 100644
--- a/src/proto/grpc/channelz/BUILD
+++ b/src/proto/grpc/channelz/BUILD
@@ -24,3 +24,10 @@ grpc_proto_library(
has_services = True,
well_known_protos = True,
)
+
+filegroup(
+ name = "channelz_proto_file",
+ srcs = [
+ "channelz.proto",
+ ],
+)
diff --git a/src/proto/grpc/health/v1/BUILD b/src/proto/grpc/health/v1/BUILD
index 97642985c9..38a7d992e0 100644
--- a/src/proto/grpc/health/v1/BUILD
+++ b/src/proto/grpc/health/v1/BUILD
@@ -29,4 +29,3 @@ filegroup(
"health.proto",
],
)
-
diff --git a/src/python/grpcio/grpc/_cython/BUILD.bazel b/src/python/grpcio/grpc/_cython/BUILD.bazel
index cfd3a51d9b..e318298d0a 100644
--- a/src/python/grpcio/grpc/_cython/BUILD.bazel
+++ b/src/python/grpcio/grpc/_cython/BUILD.bazel
@@ -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",
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/channelz.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/channelz.pyx.pxi
new file mode 100644
index 0000000000..113f7976dd
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/channelz.pyx.pxi
@@ -0,0 +1,69 @@
+# 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
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
index 5aaf31e36c..5bbc10af25 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
@@ -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
@@ -384,6 +385,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":
diff --git a/src/python/grpcio/grpc/_cython/cygrpc.pyx b/src/python/grpcio/grpc/_cython/cygrpc.pyx
index ae5c07bfc8..9ab919375c 100644
--- a/src/python/grpcio/grpc/_cython/cygrpc.pyx
+++ b/src/python/grpcio/grpc/_cython/cygrpc.pyx
@@ -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"
diff --git a/src/python/grpcio_channelz/.gitignore b/src/python/grpcio_channelz/.gitignore
new file mode 100644
index 0000000000..0c5da6b5af
--- /dev/null
+++ b/src/python/grpcio_channelz/.gitignore
@@ -0,0 +1,6 @@
+*.proto
+*_pb2.py
+*_pb2_grpc.py
+build/
+grpcio_channelz.egg-info/
+dist/
diff --git a/src/python/grpcio_channelz/MANIFEST.in b/src/python/grpcio_channelz/MANIFEST.in
new file mode 100644
index 0000000000..5597f375ba
--- /dev/null
+++ b/src/python/grpcio_channelz/MANIFEST.in
@@ -0,0 +1,3 @@
+include grpc_version.py
+recursive-include grpc_channelz *.py
+global-exclude *.pyc
diff --git a/src/python/grpcio_channelz/README.rst b/src/python/grpcio_channelz/README.rst
new file mode 100644
index 0000000000..efeaa56064
--- /dev/null
+++ b/src/python/grpcio_channelz/README.rst
@@ -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`.
diff --git a/src/python/grpcio_channelz/channelz_commands.py b/src/python/grpcio_channelz/channelz_commands.py
new file mode 100644
index 0000000000..e9ad355034
--- /dev/null
+++ b/src/python/grpcio_channelz/channelz_commands.py
@@ -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[''])
diff --git a/src/python/grpcio_channelz/grpc_channelz/__init__.py b/src/python/grpcio_channelz/grpc_channelz/__init__.py
new file mode 100644
index 0000000000..38fdfc9c5c
--- /dev/null
+++ b/src/python/grpcio_channelz/grpc_channelz/__init__.py
@@ -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.
diff --git a/src/python/grpcio_channelz/grpc_channelz/v1/BUILD.bazel b/src/python/grpcio_channelz/grpc_channelz/v1/BUILD.bazel
new file mode 100644
index 0000000000..aae8cedb76
--- /dev/null
+++ b/src/python/grpcio_channelz/grpc_channelz/v1/BUILD.bazel
@@ -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=["../../",],
+)
diff --git a/src/python/grpcio_channelz/grpc_channelz/v1/__init__.py b/src/python/grpcio_channelz/grpc_channelz/v1/__init__.py
new file mode 100644
index 0000000000..38fdfc9c5c
--- /dev/null
+++ b/src/python/grpcio_channelz/grpc_channelz/v1/__init__.py
@@ -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.
diff --git a/src/python/grpcio_channelz/grpc_channelz/v1/channelz.py b/src/python/grpcio_channelz/grpc_channelz/v1/channelz.py
new file mode 100644
index 0000000000..573b9d0d5a
--- /dev/null
+++ b/src/python/grpcio_channelz/grpc_channelz/v1/channelz.py
@@ -0,0 +1,141 @@
+# 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."""
+
+ @staticmethod
+ def GetTopChannels(request, context):
+ try:
+ return json_format.Parse(
+ cygrpc.channelz_get_top_channels(request.start_channel_id),
+ _channelz_pb2.GetTopChannelsResponse(),
+ )
+ except (ValueError, json_format.ParseError) as e:
+ context.set_code(grpc.StatusCode.INTERNAL)
+ context.set_details(str(e))
+
+ @staticmethod
+ def GetServers(request, context):
+ try:
+ return json_format.Parse(
+ cygrpc.channelz_get_servers(request.start_server_id),
+ _channelz_pb2.GetServersResponse(),
+ )
+ except (ValueError, json_format.ParseError) as e:
+ context.set_code(grpc.StatusCode.INTERNAL)
+ context.set_details(str(e))
+
+ @staticmethod
+ def GetServer(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.NOT_FOUND)
+ context.set_details(str(e))
+ except json_format.ParseError as e:
+ context.set_code(grpc.StatusCode.INTERNAL)
+ context.set_details(str(e))
+
+ @staticmethod
+ def GetServerSockets(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.NOT_FOUND)
+ context.set_details(str(e))
+ except json_format.ParseError as e:
+ context.set_code(grpc.StatusCode.INTERNAL)
+ context.set_details(str(e))
+
+ @staticmethod
+ def GetChannel(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.NOT_FOUND)
+ context.set_details(str(e))
+ except json_format.ParseError as e:
+ context.set_code(grpc.StatusCode.INTERNAL)
+ context.set_details(str(e))
+
+ @staticmethod
+ def GetSubchannel(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.NOT_FOUND)
+ context.set_details(str(e))
+ except json_format.ParseError as e:
+ context.set_code(grpc.StatusCode.INTERNAL)
+ context.set_details(str(e))
+
+ @staticmethod
+ def GetSocket(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.NOT_FOUND)
+ context.set_details(str(e))
+ except json_format.ParseError as e:
+ context.set_code(grpc.StatusCode.INTERNAL)
+ context.set_details(str(e))
+
+
+def add_channelz_servicer(server):
+ """Add Channelz servicer to a server. Channelz servicer is in charge of
+ pulling information from C-Core for entire process. It will allow the
+ server to response to Channelz queries.
+
+ The Channelz statistic is enabled by default inside C-Core. Whether the
+ statistic is enabled or not is isolated from adding Channelz servicer.
+ That means you can query Channelz info with a Channelz-disabled channel,
+ and you can add Channelz servicer to a Channelz-disabled server.
+
+ The Channelz statistic can be enabled or disabled by channel option
+ 'grpc.enable_channelz'. Set to 1 to enable, set to 0 to disable.
+
+ This is an EXPERIMENTAL API.
+
+ Args:
+ server: grpc.Server to which Channelz service will be added.
+ """
+ _channelz_pb2_grpc.add_ChannelzServicer_to_server(ChannelzServicer(),
+ server)
diff --git a/src/python/grpcio_channelz/grpc_version.py b/src/python/grpcio_channelz/grpc_version.py
new file mode 100644
index 0000000000..16356ea402
--- /dev/null
+++ b/src/python/grpcio_channelz/grpc_version.py
@@ -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'
diff --git a/src/python/grpcio_channelz/setup.py b/src/python/grpcio_channelz/setup.py
new file mode 100644
index 0000000000..a495052376
--- /dev/null
+++ b/src/python/grpcio_channelz/setup.py
@@ -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)
diff --git a/src/python/grpcio_tests/commands.py b/src/python/grpcio_tests/commands.py
index d163f6fb68..65e9a99950 100644
--- a/src/python/grpcio_tests/commands.py
+++ b/src/python/grpcio_tests/commands.py
@@ -132,7 +132,12 @@ class TestGevent(setuptools.Command):
'unit.beta._beta_features_test',
# TODO(https://github.com/grpc/grpc/issues/15411) unpin gevent version
# This test will stuck while running higher version of gevent
- 'unit._auth_context_test.AuthContextTest.testSessionResumption')
+ 'unit._auth_context_test.AuthContextTest.testSessionResumption',
+ # TODO(https://github.com/grpc/grpc/issues/17330) enable these three tests
+ 'channelz._channelz_servicer_test.ChannelzServicerTest.test_many_subchannels',
+ 'channelz._channelz_servicer_test.ChannelzServicerTest.test_many_subchannels_and_sockets',
+ 'channelz._channelz_servicer_test.ChannelzServicerTest.test_streaming_rpc'
+ )
description = 'run tests with gevent. Assumes grpc/gevent are installed'
user_options = []
diff --git a/src/python/grpcio_tests/setup.py b/src/python/grpcio_tests/setup.py
index 61c98fa038..f56425ac6d 100644
--- a/src/python/grpcio_tests/setup.py
+++ b/src/python/grpcio_tests/setup.py
@@ -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',
diff --git a/src/python/grpcio_tests/tests/channelz/BUILD.bazel b/src/python/grpcio_tests/tests/channelz/BUILD.bazel
new file mode 100644
index 0000000000..63513616e7
--- /dev/null
+++ b/src/python/grpcio_tests/tests/channelz/BUILD.bazel
@@ -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 = ["../../",],
+)
diff --git a/src/python/grpcio_tests/tests/channelz/__init__.py b/src/python/grpcio_tests/tests/channelz/__init__.py
new file mode 100644
index 0000000000..38fdfc9c5c
--- /dev/null
+++ b/src/python/grpcio_tests/tests/channelz/__init__.py
@@ -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.
diff --git a/src/python/grpcio_tests/tests/channelz/_channelz_servicer_test.py b/src/python/grpcio_tests/tests/channelz/_channelz_servicer_test.py
new file mode 100644
index 0000000000..84f8594689
--- /dev/null
+++ b/src/python/grpcio_tests/tests/channelz/_channelz_servicer_test.py
@@ -0,0 +1,494 @@
+# 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, so its `del` can be called explicitly, during
+ # the destruction process. Otherwise, if the removal of server
+ # rely on gc cycle, the test will become non-deterministic.
+ 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):
+ self._pairs = []
+ # 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.add_channelz_servicer(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()
+ _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)
+
+ @unittest.skip('Servers in core are not guaranteed to be destroyed ' \
+ 'immediately when the reference goes out of scope, so ' \
+ 'servers from multiple test cases are not hermetic. ' \
+ 'TODO(https://github.com/grpc/grpc/issues/17258)')
+ 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)
+
+ @unittest.skip('Servers in core are not guaranteed to be destroyed ' \
+ 'immediately when the reference goes out of scope, so ' \
+ 'servers from multiple test cases are not hermetic. ' \
+ 'TODO(https://github.com/grpc/grpc/issues/17258)')
+ 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)
+
+ @unittest.skip('Servers in core are not guaranteed to be destroyed ' \
+ 'immediately when the reference goes out of scope, so ' \
+ 'servers from multiple test cases are not hermetic. ' \
+ 'TODO(https://github.com/grpc/grpc/issues/17258)')
+ 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)
+
+ @unittest.skip('Servers in core are not guaranteed to be destroyed ' \
+ 'immediately when the reference goes out of scope, so ' \
+ 'servers from multiple test cases are not hermetic. ' \
+ 'TODO(https://github.com/grpc/grpc/issues/17258)')
+ 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
+
+ @unittest.skip('Servers in core are not guaranteed to be destroyed ' \
+ 'immediately when the reference goes out of scope, so ' \
+ 'servers from multiple test cases are not hermetic. ' \
+ 'TODO(https://github.com/grpc/grpc/issues/17258)')
+ 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.NOT_FOUND', 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.NOT_FOUND', 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.NOT_FOUND', 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.NOT_FOUND', 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.NOT_FOUND', str(e))
+ else:
+ self.fail('Invalid query not detected')
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/python/grpcio_tests/tests/tests.json b/src/python/grpcio_tests/tests/tests.json
index a3006d9afc..9cffd3df19 100644
--- a/src/python/grpcio_tests/tests/tests.json
+++ b/src/python/grpcio_tests/tests/tests.json
@@ -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",
diff --git a/templates/src/python/grpcio_channelz/grpc_version.py.template b/templates/src/python/grpcio_channelz/grpc_version.py.template
new file mode 100644
index 0000000000..75d510cd17
--- /dev/null
+++ b/templates/src/python/grpcio_channelz/grpc_version.py.template
@@ -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()}'
diff --git a/tools/distrib/pylint_code.sh b/tools/distrib/pylint_code.sh
index 82a818cce5..d17eb9fdb8 100755
--- a/tools/distrib/pylint_code.sh
+++ b/tools/distrib/pylint_code.sh
@@ -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'
diff --git a/tools/run_tests/artifacts/build_artifact_python.sh b/tools/run_tests/artifacts/build_artifact_python.sh
index 65f2d1c765..bc6e558204 100755
--- a/tools/run_tests/artifacts/build_artifact_python.sh
+++ b/tools/run_tests/artifacts/build_artifact_python.sh
@@ -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
diff --git a/tools/run_tests/helper_scripts/build_python.sh b/tools/run_tests/helper_scripts/build_python.sh
index 8394f07e51..e43f1fb429 100755
--- a/tools/run_tests/helper_scripts/build_python.sh
+++ b/tools/run_tests/helper_scripts/build_python.sh
@@ -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 Channelz
+$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