aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Masood Malekghassemi <soltanmm@users.noreply.github.com>2015-08-31 12:30:28 -0400
committerGravatar Masood Malekghassemi <soltanmm@users.noreply.github.com>2015-08-31 12:30:28 -0400
commitec88feb4d39af1ee8aa908c8d46618ce4fa25ffe (patch)
tree2722b5c1f8b557daf594d7e46783c9084497bee7
parent5684561251f47bd2109052692e67b94dc21de8d1 (diff)
parent9e4d0610ea5e2bb302c6185648e9e126d1dd8d9c (diff)
Merge pull request #3134 from nathanielmanistaatgoogle/beta
The rest of the Python Beta API
-rw-r--r--src/python/grpcio/grpc/beta/_server.py112
-rw-r--r--src/python/grpcio/grpc/beta/_stub.py109
-rw-r--r--src/python/grpcio/grpc/beta/beta.py383
-rw-r--r--src/python/grpcio_test/grpc_test/beta/_face_interface_test.py137
-rw-r--r--src/python/grpcio_test/grpc_test/beta/test_utilities.py54
-rw-r--r--src/python/grpcio_test/grpc_test/credentials/README1
-rwxr-xr-xsrc/python/grpcio_test/grpc_test/credentials/ca.pem15
-rwxr-xr-xsrc/python/grpcio_test/grpc_test/credentials/server1.key16
-rwxr-xr-xsrc/python/grpcio_test/grpc_test/credentials/server1.pem16
-rw-r--r--src/python/grpcio_test/grpc_test/resources.py56
-rw-r--r--src/python/grpcio_test/setup.py5
-rwxr-xr-xtools/run_tests/run_python.sh1
12 files changed, 901 insertions, 4 deletions
diff --git a/src/python/grpcio/grpc/beta/_server.py b/src/python/grpcio/grpc/beta/_server.py
new file mode 100644
index 0000000000..4e46ffd17f
--- /dev/null
+++ b/src/python/grpcio/grpc/beta/_server.py
@@ -0,0 +1,112 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Beta API server implementation."""
+
+import threading
+
+from grpc._links import service
+from grpc.framework.core import implementations as _core_implementations
+from grpc.framework.crust import implementations as _crust_implementations
+from grpc.framework.foundation import logging_pool
+from grpc.framework.interfaces.links import utilities
+
+_DEFAULT_POOL_SIZE = 8
+_DEFAULT_TIMEOUT = 300
+_MAXIMUM_TIMEOUT = 24 * 60 * 60
+
+
+def _disassemble(grpc_link, end_link, pool, event, grace):
+ grpc_link.begin_stop()
+ end_link.stop(grace).wait()
+ grpc_link.end_stop()
+ grpc_link.join_link(utilities.NULL_LINK)
+ end_link.join_link(utilities.NULL_LINK)
+ if pool is not None:
+ pool.shutdown(wait=True)
+ event.set()
+
+
+class Server(object):
+
+ def __init__(self, grpc_link, end_link, pool):
+ self._grpc_link = grpc_link
+ self._end_link = end_link
+ self._pool = pool
+
+ def add_insecure_port(self, address):
+ return self._grpc_link.add_port(address, None)
+
+ def add_secure_port(self, address, intermediary_low_server_credentials):
+ return self._grpc_link.add_port(
+ address, intermediary_low_server_credentials)
+
+ def start(self):
+ self._grpc_link.join_link(self._end_link)
+ self._end_link.join_link(self._grpc_link)
+ self._grpc_link.start()
+ self._end_link.start()
+
+ def stop(self, grace):
+ stop_event = threading.Event()
+ if 0 < grace:
+ disassembly_thread = threading.Thread(
+ target=_disassemble,
+ args=(
+ self._grpc_link, self._end_link, self._pool, stop_event, grace,))
+ disassembly_thread.start()
+ return stop_event
+ else:
+ _disassemble(self._grpc_link, self._end_link, self._pool, stop_event, 0)
+ return stop_event
+
+
+def server(
+ implementations, multi_implementation, request_deserializers,
+ response_serializers, thread_pool, thread_pool_size, default_timeout,
+ maximum_timeout):
+ if thread_pool is None:
+ service_thread_pool = logging_pool.pool(
+ _DEFAULT_POOL_SIZE if thread_pool_size is None else thread_pool_size)
+ assembly_thread_pool = service_thread_pool
+ else:
+ service_thread_pool = thread_pool
+ assembly_thread_pool = None
+
+ servicer = _crust_implementations.servicer(
+ implementations, multi_implementation, service_thread_pool)
+
+ grpc_link = service.service_link(request_deserializers, response_serializers)
+
+ end_link = _core_implementations.service_end_link(
+ servicer,
+ _DEFAULT_TIMEOUT if default_timeout is None else default_timeout,
+ _MAXIMUM_TIMEOUT if maximum_timeout is None else maximum_timeout)
+
+ return Server(grpc_link, end_link, assembly_thread_pool)
diff --git a/src/python/grpcio/grpc/beta/_stub.py b/src/python/grpcio/grpc/beta/_stub.py
new file mode 100644
index 0000000000..178f06d21e
--- /dev/null
+++ b/src/python/grpcio/grpc/beta/_stub.py
@@ -0,0 +1,109 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Beta API stub implementation."""
+
+import threading
+
+from grpc._links import invocation
+from grpc.framework.core import implementations as _core_implementations
+from grpc.framework.crust import implementations as _crust_implementations
+from grpc.framework.foundation import logging_pool
+from grpc.framework.interfaces.links import utilities
+
+_DEFAULT_POOL_SIZE = 6
+
+
+class _AutoIntermediary(object):
+
+ def __init__(self, delegate, on_deletion):
+ self._delegate = delegate
+ self._on_deletion = on_deletion
+
+ def __getattr__(self, attr):
+ return getattr(self._delegate, attr)
+
+ def __del__(self):
+ self._on_deletion()
+
+
+def _assemble(
+ channel, host, request_serializers, response_deserializers, thread_pool,
+ thread_pool_size):
+ end_link = _core_implementations.invocation_end_link()
+ grpc_link = invocation.invocation_link(
+ channel, host, request_serializers, response_deserializers)
+ if thread_pool is None:
+ invocation_pool = logging_pool.pool(
+ _DEFAULT_POOL_SIZE if thread_pool_size is None else thread_pool_size)
+ assembly_pool = invocation_pool
+ else:
+ invocation_pool = thread_pool
+ assembly_pool = None
+ end_link.join_link(grpc_link)
+ grpc_link.join_link(end_link)
+ end_link.start()
+ grpc_link.start()
+ return end_link, grpc_link, invocation_pool, assembly_pool
+
+
+def _disassemble(end_link, grpc_link, pool):
+ end_link.stop(24 * 60 * 60).wait()
+ grpc_link.stop()
+ end_link.join_link(utilities.NULL_LINK)
+ grpc_link.join_link(utilities.NULL_LINK)
+ if pool is not None:
+ pool.shutdown(wait=True)
+
+
+def _wrap_assembly(stub, end_link, grpc_link, assembly_pool):
+ disassembly_thread = threading.Thread(
+ target=_disassemble, args=(end_link, grpc_link, assembly_pool))
+ return _AutoIntermediary(stub, disassembly_thread.start)
+
+
+def generic_stub(
+ channel, host, request_serializers, response_deserializers, thread_pool,
+ thread_pool_size):
+ end_link, grpc_link, invocation_pool, assembly_pool = _assemble(
+ channel, host, request_serializers, response_deserializers, thread_pool,
+ thread_pool_size)
+ stub = _crust_implementations.generic_stub(end_link, invocation_pool)
+ return _wrap_assembly(stub, end_link, grpc_link, assembly_pool)
+
+
+def dynamic_stub(
+ channel, host, service, cardinalities, request_serializers,
+ response_deserializers, thread_pool, thread_pool_size):
+ end_link, grpc_link, invocation_pool, assembly_pool = _assemble(
+ channel, host, request_serializers, response_deserializers, thread_pool,
+ thread_pool_size)
+ stub = _crust_implementations.dynamic_stub(
+ end_link, service, cardinalities, invocation_pool)
+ return _wrap_assembly(stub, end_link, grpc_link, assembly_pool)
diff --git a/src/python/grpcio/grpc/beta/beta.py b/src/python/grpcio/grpc/beta/beta.py
index 40cad5e486..640e4eb86b 100644
--- a/src/python/grpcio/grpc/beta/beta.py
+++ b/src/python/grpcio/grpc/beta/beta.py
@@ -27,13 +27,21 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""Entry points into gRPC Python Beta."""
+"""Entry points into the Beta API of gRPC Python."""
+# threading is referenced from specification in this module.
+import abc
import enum
+import threading # pylint: disable=unused-import
-from grpc._adapter import _low
+# cardinality and face are referenced from specification in this module.
+from grpc._adapter import _intermediary_low
from grpc._adapter import _types
from grpc.beta import _connectivity_channel
+from grpc.beta import _server
+from grpc.beta import _stub
+from grpc.framework.common import cardinality # pylint: disable=unused-import
+from grpc.framework.interfaces.face import face # pylint: disable=unused-import
_CHANNEL_SUBSCRIPTION_CALLBACK_ERROR_LOG_MESSAGE = (
'Exception calling channel subscription callback!')
@@ -65,6 +73,39 @@ _LOW_CONNECTIVITY_STATE_TO_CHANNEL_CONNECTIVITY = {
}
+class ClientCredentials(object):
+ """A value encapsulating the data required to create a secure Channel.
+
+ This class and its instances have no supported interface - it exists to define
+ the type of its instances and its instances exist to be passed to other
+ functions.
+ """
+
+ def __init__(self, low_credentials, intermediary_low_credentials):
+ self._low_credentials = low_credentials
+ self._intermediary_low_credentials = intermediary_low_credentials
+
+
+def ssl_client_credentials(root_certificates, private_key, certificate_chain):
+ """Creates a ClientCredentials for use with an SSL-enabled Channel.
+
+ Args:
+ root_certificates: The PEM-encoded root certificates or None to ask for
+ them to be retrieved from a default location.
+ private_key: The PEM-encoded private key to use or None if no private key
+ should be used.
+ certificate_chain: The PEM-encoded certificate chain to use or None if no
+ certificate chain should be used.
+
+ Returns:
+ A ClientCredentials for use with an SSL-enabled Channel.
+ """
+ intermediary_low_credentials = _intermediary_low.ClientCredentials(
+ root_certificates, private_key, certificate_chain)
+ return ClientCredentials(
+ intermediary_low_credentials._internal, intermediary_low_credentials) # pylint: disable=protected-access
+
+
class Channel(object):
"""A channel to a remote host through which RPCs may be conducted.
@@ -73,7 +114,9 @@ class Channel(object):
unsupported.
"""
- def __init__(self, low_channel):
+ def __init__(self, low_channel, intermediary_low_channel):
+ self._low_channel = low_channel
+ self._intermediary_low_channel = intermediary_low_channel
self._connectivity_channel = _connectivity_channel.ConnectivityChannel(
low_channel, _LOW_CONNECTIVITY_STATE_TO_CHANNEL_CONNECTIVITY)
@@ -111,4 +154,336 @@ def create_insecure_channel(host, port):
Returns:
A Channel to the remote host through which RPCs may be conducted.
"""
- return Channel(_low.Channel('%s:%d' % (host, port), ()))
+ intermediary_low_channel = _intermediary_low.Channel(
+ '%s:%d' % (host, port), None)
+ return Channel(intermediary_low_channel._internal, intermediary_low_channel) # pylint: disable=protected-access
+
+
+def create_secure_channel(host, port, client_credentials):
+ """Creates a secure Channel to a remote host.
+
+ Args:
+ host: The name of the remote host to which to connect.
+ port: The port of the remote host to which to connect.
+ client_credentials: A ClientCredentials.
+
+ Returns:
+ A secure Channel to the remote host through which RPCs may be conducted.
+ """
+ intermediary_low_channel = _intermediary_low.Channel(
+ '%s:%d' % (host, port), client_credentials.intermediary_low_credentials)
+ return Channel(intermediary_low_channel._internal, intermediary_low_channel) # pylint: disable=protected-access
+
+
+class StubOptions(object):
+ """A value encapsulating the various options for creation of a Stub.
+
+ This class and its instances have no supported interface - it exists to define
+ the type of its instances and its instances exist to be passed to other
+ functions.
+ """
+
+ def __init__(
+ self, host, request_serializers, response_deserializers,
+ metadata_transformer, thread_pool, thread_pool_size):
+ self.host = host
+ self.request_serializers = request_serializers
+ self.response_deserializers = response_deserializers
+ self.metadata_transformer = metadata_transformer
+ self.thread_pool = thread_pool
+ self.thread_pool_size = thread_pool_size
+
+_EMPTY_STUB_OPTIONS = StubOptions(
+ None, None, None, None, None, None)
+
+
+def stub_options(
+ host=None, request_serializers=None, response_deserializers=None,
+ metadata_transformer=None, thread_pool=None, thread_pool_size=None):
+ """Creates a StubOptions value to be passed at stub creation.
+
+ All parameters are optional and should always be passed by keyword.
+
+ Args:
+ host: A host string to set on RPC calls.
+ request_serializers: A dictionary from service name-method name pair to
+ request serialization behavior.
+ response_deserializers: A dictionary from service name-method name pair to
+ response deserialization behavior.
+ metadata_transformer: A callable that given a metadata object produces
+ another metadata object to be used in the underlying communication on the
+ wire.
+ thread_pool: A thread pool to use in stubs.
+ thread_pool_size: The size of thread pool to create for use in stubs;
+ ignored if thread_pool has been passed.
+
+ Returns:
+ A StubOptions value created from the passed parameters.
+ """
+ return StubOptions(
+ host, request_serializers, response_deserializers,
+ metadata_transformer, thread_pool, thread_pool_size)
+
+
+def generic_stub(channel, options=None):
+ """Creates a face.GenericStub on which RPCs can be made.
+
+ Args:
+ channel: A Channel for use by the created stub.
+ options: A StubOptions customizing the created stub.
+
+ Returns:
+ A face.GenericStub on which RPCs can be made.
+ """
+ effective_options = _EMPTY_STUB_OPTIONS if options is None else options
+ return _stub.generic_stub(
+ channel._intermediary_low_channel, effective_options.host, # pylint: disable=protected-access
+ effective_options.request_serializers,
+ effective_options.response_deserializers, effective_options.thread_pool,
+ effective_options.thread_pool_size)
+
+
+def dynamic_stub(channel, service, cardinalities, options=None):
+ """Creates a face.DynamicStub with which RPCs can be invoked.
+
+ Args:
+ channel: A Channel for the returned face.DynamicStub to use.
+ service: The package-qualified full name of the service.
+ cardinalities: A dictionary from RPC method name to cardinality.Cardinality
+ value identifying the cardinality of the RPC method.
+ options: An optional StubOptions value further customizing the functionality
+ of the returned face.DynamicStub.
+
+ Returns:
+ A face.DynamicStub with which RPCs can be invoked.
+ """
+ effective_options = StubOptions() if options is None else options
+ return _stub.dynamic_stub(
+ channel._intermediary_low_channel, effective_options.host, service, # pylint: disable=protected-access
+ cardinalities, effective_options.request_serializers,
+ effective_options.response_deserializers, effective_options.thread_pool,
+ effective_options.thread_pool_size)
+
+
+class ServerCredentials(object):
+ """A value encapsulating the data required to open a secure port on a Server.
+
+ This class and its instances have no supported interface - it exists to define
+ the type of its instances and its instances exist to be passed to other
+ functions.
+ """
+
+ def __init__(self, low_credentials, intermediary_low_credentials):
+ self._low_credentials = low_credentials
+ self._intermediary_low_credentials = intermediary_low_credentials
+
+
+def ssl_server_credentials(
+ private_key_certificate_chain_pairs, root_certificates=None,
+ require_client_auth=False):
+ """Creates a ServerCredentials for use with an SSL-enabled Server.
+
+ Args:
+ private_key_certificate_chain_pairs: A nonempty sequence each element of
+ which is a pair the first element of which is a PEM-encoded private key
+ and the second element of which is the corresponding PEM-encoded
+ certificate chain.
+ root_certificates: PEM-encoded client root certificates to be used for
+ verifying authenticated clients. If omitted, require_client_auth must also
+ be omitted or be False.
+ require_client_auth: A boolean indicating whether or not to require clients
+ to be authenticated. May only be True if root_certificates is not None.
+
+ Returns:
+ A ServerCredentials for use with an SSL-enabled Server.
+ """
+ if len(private_key_certificate_chain_pairs) == 0:
+ raise ValueError(
+ 'At least one private key-certificate chain pairis required!')
+ elif require_client_auth and root_certificates is None:
+ raise ValueError(
+ 'Illegal to require client auth without providing root certificates!')
+ else:
+ intermediary_low_credentials = _intermediary_low.ServerCredentials(
+ root_certificates, private_key_certificate_chain_pairs,
+ require_client_auth)
+ return ServerCredentials(
+ intermediary_low_credentials._internal, intermediary_low_credentials) # pylint: disable=protected-access
+
+
+class Server(object):
+ """Services RPCs."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def add_insecure_port(self, address):
+ """Reserves a port for insecure RPC service once this Server becomes active.
+
+ This method may only be called before calling this Server's start method is
+ called.
+
+ Args:
+ address: The address for which to open a port.
+
+ Returns:
+ An integer port on which RPCs will be serviced after this link has been
+ started. This is typically the same number as the port number contained
+ in the passed address, but will likely be different if the port number
+ contained in the passed address was zero.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def add_secure_port(self, address, server_credentials):
+ """Reserves a port for secure RPC service after this Server becomes active.
+
+ This method may only be called before calling this Server's start method is
+ called.
+
+ Args:
+ address: The address for which to open a port.
+ server_credentials: A ServerCredentials.
+
+ Returns:
+ An integer port on which RPCs will be serviced after this link has been
+ started. This is typically the same number as the port number contained
+ in the passed address, but will likely be different if the port number
+ contained in the passed address was zero.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def start(self):
+ """Starts this Server's service of RPCs.
+
+ This method may only be called while the server is not serving RPCs (i.e. it
+ is not idempotent).
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def stop(self, grace):
+ """Stops this Server's service of RPCs.
+
+ All calls to this method immediately stop service of new RPCs. When existing
+ RPCs are aborted is controlled by the grace period parameter passed to this
+ method.
+
+ This method may be called at any time and is idempotent. Passing a smaller
+ grace value than has been passed in a previous call will have the effect of
+ stopping the Server sooner. Passing a larger grace value than has been
+ passed in a previous call will not have the effect of stopping the sooner
+ later.
+
+ Args:
+ grace: A duration of time in seconds to allow existing RPCs to complete
+ before being aborted by this Server's stopping. May be zero for
+ immediate abortion of all in-progress RPCs.
+
+ Returns:
+ A threading.Event that will be set when this Server has completely
+ stopped. The returned event may not be set until after the full grace
+ period (if some ongoing RPC continues for the full length of the period)
+ of it may be set much sooner (such as if this Server had no RPCs underway
+ at the time it was stopped or if all RPCs that it had underway completed
+ very early in the grace period).
+ """
+ raise NotImplementedError()
+
+
+class ServerOptions(object):
+ """A value encapsulating the various options for creation of a Server.
+
+ This class and its instances have no supported interface - it exists to define
+ the type of its instances and its instances exist to be passed to other
+ functions.
+ """
+
+ def __init__(
+ self, multi_method_implementation, request_deserializers,
+ response_serializers, thread_pool, thread_pool_size, default_timeout,
+ maximum_timeout):
+ self.multi_method_implementation = multi_method_implementation
+ self.request_deserializers = request_deserializers
+ self.response_serializers = response_serializers
+ self.thread_pool = thread_pool
+ self.thread_pool_size = thread_pool_size
+ self.default_timeout = default_timeout
+ self.maximum_timeout = maximum_timeout
+
+_EMPTY_SERVER_OPTIONS = ServerOptions(
+ None, None, None, None, None, None, None)
+
+
+def server_options(
+ multi_method_implementation=None, request_deserializers=None,
+ response_serializers=None, thread_pool=None, thread_pool_size=None,
+ default_timeout=None, maximum_timeout=None):
+ """Creates a ServerOptions value to be passed at server creation.
+
+ All parameters are optional and should always be passed by keyword.
+
+ Args:
+ multi_method_implementation: A face.MultiMethodImplementation to be called
+ to service an RPC if the server has no specific method implementation for
+ the name of the RPC for which service was requested.
+ request_deserializers: A dictionary from service name-method name pair to
+ request deserialization behavior.
+ response_serializers: A dictionary from service name-method name pair to
+ response serialization behavior.
+ thread_pool: A thread pool to use in stubs.
+ thread_pool_size: The size of thread pool to create for use in stubs;
+ ignored if thread_pool has been passed.
+ default_timeout: A duration in seconds to allow for RPC service when
+ servicing RPCs that did not include a timeout value when invoked.
+ maximum_timeout: A duration in seconds to allow for RPC service when
+ servicing RPCs no matter what timeout value was passed when the RPC was
+ invoked.
+
+ Returns:
+ A StubOptions value created from the passed parameters.
+ """
+ return ServerOptions(
+ multi_method_implementation, request_deserializers, response_serializers,
+ thread_pool, thread_pool_size, default_timeout, maximum_timeout)
+
+
+class _Server(Server):
+
+ def __init__(self, underserver):
+ self._underserver = underserver
+
+ def add_insecure_port(self, address):
+ return self._underserver.add_insecure_port(address)
+
+ def add_secure_port(self, address, server_credentials):
+ return self._underserver.add_secure_port(
+ address, server_credentials._intermediary_low_credentials) # pylint: disable=protected-access
+
+ def start(self):
+ self._underserver.start()
+
+ def stop(self, grace):
+ return self._underserver.stop(grace)
+
+
+def server(service_implementations, options=None):
+ """Creates a Server with which RPCs can be serviced.
+
+ Args:
+ service_implementations: A dictionary from service name-method name pair to
+ face.MethodImplementation.
+ options: An optional ServerOptions value further customizing the
+ functionality of the returned Server.
+
+ Returns:
+ A Server with which RPCs can be serviced.
+ """
+ effective_options = _EMPTY_SERVER_OPTIONS if options is None else options
+ underserver = _server.server(
+ service_implementations, effective_options.multi_method_implementation,
+ effective_options.request_deserializers,
+ effective_options.response_serializers, effective_options.thread_pool,
+ effective_options.thread_pool_size, effective_options.default_timeout,
+ effective_options.maximum_timeout)
+ return _Server(underserver)
diff --git a/src/python/grpcio_test/grpc_test/beta/_face_interface_test.py b/src/python/grpcio_test/grpc_test/beta/_face_interface_test.py
new file mode 100644
index 0000000000..ce4c59c0ee
--- /dev/null
+++ b/src/python/grpcio_test/grpc_test/beta/_face_interface_test.py
@@ -0,0 +1,137 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Tests Face interface compliance of the gRPC Python Beta API."""
+
+import collections
+import unittest
+
+from grpc._adapter import _intermediary_low
+from grpc.beta import beta
+from grpc_test import resources
+from grpc_test import test_common as grpc_test_common
+from grpc_test.beta import test_utilities
+from grpc_test.framework.common import test_constants
+from grpc_test.framework.interfaces.face import test_cases
+from grpc_test.framework.interfaces.face import test_interfaces
+
+_SERVER_HOST_OVERRIDE = 'foo.test.google.fr'
+
+
+class _SerializationBehaviors(
+ collections.namedtuple(
+ '_SerializationBehaviors',
+ ('request_serializers', 'request_deserializers', 'response_serializers',
+ 'response_deserializers',))):
+ pass
+
+
+def _serialization_behaviors_from_test_methods(test_methods):
+ request_serializers = {}
+ request_deserializers = {}
+ response_serializers = {}
+ response_deserializers = {}
+ for (group, method), test_method in test_methods.iteritems():
+ request_serializers[group, method] = test_method.serialize_request
+ request_deserializers[group, method] = test_method.deserialize_request
+ response_serializers[group, method] = test_method.serialize_response
+ response_deserializers[group, method] = test_method.deserialize_response
+ return _SerializationBehaviors(
+ request_serializers, request_deserializers, response_serializers,
+ response_deserializers)
+
+
+class _Implementation(test_interfaces.Implementation):
+
+ def instantiate(
+ self, methods, method_implementations, multi_method_implementation):
+ serialization_behaviors = _serialization_behaviors_from_test_methods(
+ methods)
+ # TODO(nathaniel): Add a "groups" attribute to _digest.TestServiceDigest.
+ service = next(iter(methods))[0]
+ # TODO(nathaniel): Add a "cardinalities_by_group" attribute to
+ # _digest.TestServiceDigest.
+ cardinalities = {
+ method: method_object.cardinality()
+ for (group, method), method_object in methods.iteritems()}
+
+ server_options = beta.server_options(
+ request_deserializers=serialization_behaviors.request_deserializers,
+ response_serializers=serialization_behaviors.response_serializers,
+ thread_pool_size=test_constants.POOL_SIZE)
+ server = beta.server(method_implementations, options=server_options)
+ server_credentials = beta.ssl_server_credentials(
+ [(resources.private_key(), resources.certificate_chain(),),])
+ port = server.add_secure_port('[::]:0', server_credentials)
+ server.start()
+ client_credentials = beta.ssl_client_credentials(
+ resources.test_root_certificates(), None, None)
+ channel = test_utilities.create_not_really_secure_channel(
+ 'localhost', port, client_credentials, _SERVER_HOST_OVERRIDE)
+ stub_options = beta.stub_options(
+ request_serializers=serialization_behaviors.request_serializers,
+ response_deserializers=serialization_behaviors.response_deserializers,
+ thread_pool_size=test_constants.POOL_SIZE)
+ generic_stub = beta.generic_stub(channel, options=stub_options)
+ dynamic_stub = beta.dynamic_stub(
+ channel, service, cardinalities, options=stub_options)
+ return generic_stub, {service: dynamic_stub}, server
+
+ def destantiate(self, memo):
+ memo.stop(test_constants.SHORT_TIMEOUT).wait()
+
+ def invocation_metadata(self):
+ return grpc_test_common.INVOCATION_INITIAL_METADATA
+
+ def initial_metadata(self):
+ return grpc_test_common.SERVICE_INITIAL_METADATA
+
+ def terminal_metadata(self):
+ return grpc_test_common.SERVICE_TERMINAL_METADATA
+
+ def code(self):
+ return _intermediary_low.Code.OK
+
+ def details(self):
+ return grpc_test_common.DETAILS
+
+ def metadata_transmitted(self, original_metadata, transmitted_metadata):
+ return original_metadata is None or grpc_test_common.metadata_transmitted(
+ original_metadata, transmitted_metadata)
+
+
+def load_tests(loader, tests, pattern):
+ return unittest.TestSuite(
+ tests=tuple(
+ loader.loadTestsFromTestCase(test_case_class)
+ for test_case_class in test_cases.test_cases(_Implementation())))
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/python/grpcio_test/grpc_test/beta/test_utilities.py b/src/python/grpcio_test/grpc_test/beta/test_utilities.py
new file mode 100644
index 0000000000..338670478d
--- /dev/null
+++ b/src/python/grpcio_test/grpc_test/beta/test_utilities.py
@@ -0,0 +1,54 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Test-appropriate entry points into the gRPC Python Beta API."""
+
+from grpc._adapter import _intermediary_low
+from grpc.beta import beta
+
+
+def create_not_really_secure_channel(
+ host, port, client_credentials, server_host_override):
+ """Creates an insecure Channel to a remote host.
+
+ Args:
+ host: The name of the remote host to which to connect.
+ port: The port of the remote host to which to connect.
+ client_credentials: The beta.ClientCredentials with which to connect.
+ server_host_override: The target name used for SSL host name checking.
+
+ Returns:
+ A beta.Channel to the remote host through which RPCs may be conducted.
+ """
+ hostport = '%s:%d' % (host, port)
+ intermediary_low_channel = _intermediary_low.Channel(
+ hostport, client_credentials._intermediary_low_credentials,
+ server_host_override=server_host_override)
+ return beta.Channel(
+ intermediary_low_channel._internal, intermediary_low_channel)
diff --git a/src/python/grpcio_test/grpc_test/credentials/README b/src/python/grpcio_test/grpc_test/credentials/README
new file mode 100644
index 0000000000..cb20dcb49f
--- /dev/null
+++ b/src/python/grpcio_test/grpc_test/credentials/README
@@ -0,0 +1 @@
+These are test keys *NOT* to be used in production.
diff --git a/src/python/grpcio_test/grpc_test/credentials/ca.pem b/src/python/grpcio_test/grpc_test/credentials/ca.pem
new file mode 100755
index 0000000000..6c8511a73c
--- /dev/null
+++ b/src/python/grpcio_test/grpc_test/credentials/ca.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla
+Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
+YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT
+BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7
++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu
+g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd
+Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau
+sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m
+oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG
+Dfcog5wrJytaQ6UA0wE=
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_test/grpc_test/credentials/server1.key b/src/python/grpcio_test/grpc_test/credentials/server1.key
new file mode 100755
index 0000000000..143a5b8765
--- /dev/null
+++ b/src/python/grpcio_test/grpc_test/credentials/server1.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD
+M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf
+3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY
+AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm
+V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY
+tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p
+dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q
+K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR
+81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff
+DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd
+aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2
+ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3
+XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe
+F98XJ7tIFfJq
+-----END PRIVATE KEY-----
diff --git a/src/python/grpcio_test/grpc_test/credentials/server1.pem b/src/python/grpcio_test/grpc_test/credentials/server1.pem
new file mode 100755
index 0000000000..8e582e571f
--- /dev/null
+++ b/src/python/grpcio_test/grpc_test/credentials/server1.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5
+MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
+BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl
+c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs
+JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO
+RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30
+3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL
+BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6
+b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ
+KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS
+wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e
+aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s=
+-----END CERTIFICATE-----
diff --git a/src/python/grpcio_test/grpc_test/resources.py b/src/python/grpcio_test/grpc_test/resources.py
new file mode 100644
index 0000000000..2c3045313d
--- /dev/null
+++ b/src/python/grpcio_test/grpc_test/resources.py
@@ -0,0 +1,56 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Constants and functions for data used in interoperability testing."""
+
+import os
+
+import pkg_resources
+
+_ROOT_CERTIFICATES_RESOURCE_PATH = 'credentials/ca.pem'
+_PRIVATE_KEY_RESOURCE_PATH = 'credentials/server1.key'
+_CERTIFICATE_CHAIN_RESOURCE_PATH = 'credentials/server1.pem'
+
+
+def test_root_certificates():
+ return pkg_resources.resource_string(
+ __name__, _ROOT_CERTIFICATES_RESOURCE_PATH)
+
+
+def prod_root_certificates():
+ return open(os.environ['SSL_CERT_FILE'], mode='rb').read()
+
+
+def private_key():
+ return pkg_resources.resource_string(__name__, _PRIVATE_KEY_RESOURCE_PATH)
+
+
+def certificate_chain():
+ return pkg_resources.resource_string(
+ __name__, _CERTIFICATE_CHAIN_RESOURCE_PATH)
diff --git a/src/python/grpcio_test/setup.py b/src/python/grpcio_test/setup.py
index 898ea204ac..802dd1e53a 100644
--- a/src/python/grpcio_test/setup.py
+++ b/src/python/grpcio_test/setup.py
@@ -55,6 +55,11 @@ _PACKAGE_DATA = {
'grpc_protoc_plugin': [
'test.proto',
],
+ 'grpc_test': [
+ 'credentials/ca.pem',
+ 'credentials/server1.key',
+ 'credentials/server1.pem',
+ ],
}
_SETUP_REQUIRES = (
diff --git a/tools/run_tests/run_python.sh b/tools/run_tests/run_python.sh
index fe5685f793..977b02fd94 100755
--- a/tools/run_tests/run_python.sh
+++ b/tools/run_tests/run_python.sh
@@ -46,6 +46,7 @@ source "python"$PYVER"_virtual_environment"/bin/activate
# the team...
"python"$PYVER -m grpc_test._core_over_links_base_interface_test
"python"$PYVER -m grpc_test._crust_over_core_over_links_face_interface_test
+"python"$PYVER -m grpc_test.beta._face_interface_test
"python"$PYVER -m grpc_test.framework._crust_over_core_face_interface_test
"python"$PYVER -m grpc_test.framework.core._base_interface_test