Skip to content

Commit

Permalink
Merge pull request grpc#962 from nathanielmanistaatgoogle/interop
Browse files Browse the repository at this point in the history
Secure python interop (plus Python<->Python interop unit tests)
  • Loading branch information
soltanmm committed Mar 7, 2015
2 parents a65e463 + a25af85 commit 3aca2a6
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 31 deletions.
56 changes: 56 additions & 0 deletions src/python/interop/interop/_insecure_interop_test.py
Original file line number Diff line number Diff line change
@@ -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.

"""Insecure client-server interoperability as a unit test."""

import unittest

from grpc.early_adopter import implementations

from interop import _interop_test_case
from interop import methods


class InsecureInteropTest(
_interop_test_case.InteropTestCase,
unittest.TestCase):

def setUp(self):
self.server = implementations.insecure_server(methods.SERVER_METHODS, 0)
self.server.start()
port = self.server.port()
self.stub = implementations.insecure_stub(
methods.CLIENT_METHODS, 'localhost', port)

def tearDown(self):
self.server.stop()


if __name__ == '__main__':
unittest.main()
55 changes: 55 additions & 0 deletions src/python/interop/interop/_interop_test_case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 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.

"""Common code for unit tests of the interoperability test code."""

from interop import methods


class InteropTestCase(object):
"""Unit test methods.
This class must be mixed in with unittest.TestCase and a class that defines
setUp and tearDown methods that manage a stub attribute.
"""

def testEmptyUnary(self):
methods.TestCase.EMPTY_UNARY.test_interoperability(self.stub)

def testLargeUnary(self):
methods.TestCase.LARGE_UNARY.test_interoperability(self.stub)

def testServerStreaming(self):
methods.TestCase.SERVER_STREAMING.test_interoperability(self.stub)

def testClientStreaming(self):
methods.TestCase.CLIENT_STREAMING.test_interoperability(self.stub)

def testPingPong(self):
methods.TestCase.PING_PONG.test_interoperability(self.stub)
63 changes: 63 additions & 0 deletions src/python/interop/interop/_secure_interop_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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.

"""Secure client-server interoperability as a unit test."""

import unittest

from grpc.early_adopter import implementations

from interop import _interop_test_case
from interop import methods
from interop import resources

_SERVER_HOST_OVERRIDE = 'foo.test.google.fr'


class SecureInteropTest(
_interop_test_case.InteropTestCase,
unittest.TestCase):

def setUp(self):
self.server = implementations.secure_server(
methods.SERVER_METHODS, 0, resources.private_key(),
resources.certificate_chain())
self.server.start()
port = self.server.port()
self.stub = implementations.secure_stub(
methods.CLIENT_METHODS, 'localhost', port,
resources.test_root_certificates(), None, None,
server_host_override=_SERVER_HOST_OVERRIDE)

def tearDown(self):
self.server.stop()


if __name__ == '__main__':
unittest.main()
15 changes: 12 additions & 3 deletions src/python/interop/interop/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,30 @@ def _stub(args):
root_certificates = resources.test_root_certificates()
else:
root_certificates = resources.prod_root_certificates()
# TODO(nathaniel): server host override.

stub = implementations.secure_stub(
methods.CLIENT_METHODS, args.server_host, args.server_port,
root_certificates, None, None)
root_certificates, None, None,
server_host_override=args.server_host_override)
else:
stub = implementations.insecure_stub(
methods.CLIENT_METHODS, args.server_host, args.server_port)
return stub


def _test_case_from_arg(test_case_arg):
for test_case in methods.TestCase:
if test_case_arg == test_case.value:
return test_case
else:
raise ValueError('No test case "%s"!' % test_case_arg)


def _test_interoperability():
args = _args()
stub = _stub(args)
methods.test_interoperability(args.test_case, stub)
test_case = _test_case_from_arg(args.test_case)
test_case.test_interoperability(stub)


if __name__ == '__main__':
Expand Down
35 changes: 22 additions & 13 deletions src/python/interop/interop/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

"""Implementations of interoperability test methods."""

import enum
import threading

from grpc.early_adopter import utilities
Expand Down Expand Up @@ -265,16 +266,24 @@ def _ping_pong(stub):
pipe.close()


def test_interoperability(test_case, stub):
if test_case == 'empty_unary':
_empty_unary(stub)
elif test_case == 'large_unary':
_large_unary(stub)
elif test_case == 'server_streaming':
_server_streaming(stub)
elif test_case == 'client_streaming':
_client_streaming(stub)
elif test_case == 'ping_pong':
_ping_pong(stub)
else:
raise NotImplementedError('Test case "%s" not implemented!')
@enum.unique
class TestCase(enum.Enum):
EMPTY_UNARY = 'empty_unary'
LARGE_UNARY = 'large_unary'
SERVER_STREAMING = 'server_streaming'
CLIENT_STREAMING = 'client_streaming'
PING_PONG = 'ping_pong'

def test_interoperability(self, stub):
if self is TestCase.EMPTY_UNARY:
_empty_unary(stub)
elif self is TestCase.LARGE_UNARY:
_large_unary(stub)
elif self is TestCase.SERVER_STREAMING:
_server_streaming(stub)
elif self is TestCase.CLIENT_STREAMING:
_client_streaming(stub)
elif self is TestCase.PING_PONG:
_ping_pong(stub)
else:
raise NotImplementedError('Test case "%s" not implemented!' % self.name)
3 changes: 2 additions & 1 deletion src/python/src/grpc/_adapter/_c_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ def testCompletionQueue(self):
def testChannel(self):
_c.init()

channel = _c.Channel('test host:12345', None)
channel = _c.Channel(
'test host:12345', None, server_host_override='ignored')
del channel

_c.shut_down()
Expand Down
28 changes: 22 additions & 6 deletions src/python/src/grpc/_adapter/_channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,35 @@
static int pygrpc_channel_init(Channel *self, PyObject *args, PyObject *kwds) {
const char *hostport;
PyObject *client_credentials;
static char *kwlist[] = {"hostport", "client_credentials", NULL};
char *server_host_override = NULL;
static char *kwlist[] = {"hostport", "client_credentials",
"server_host_override", NULL};
grpc_arg server_host_override_arg;
grpc_channel_args channel_args;

if (!(PyArg_ParseTupleAndKeywords(args, kwds, "sO:Channel", kwlist,
&hostport, &client_credentials))) {
if (!(PyArg_ParseTupleAndKeywords(args, kwds, "sO|z:Channel", kwlist,
&hostport, &client_credentials,
&server_host_override))) {
return -1;
}
if (client_credentials == Py_None) {
self->c_channel = grpc_channel_create(hostport, NULL);
return 0;
} else {
self->c_channel = grpc_secure_channel_create(
((ClientCredentials *)client_credentials)->c_client_credentials,
hostport, NULL);
if (server_host_override == NULL) {
self->c_channel = grpc_secure_channel_create(
((ClientCredentials *)client_credentials)->c_client_credentials,
hostport, NULL);
} else {
server_host_override_arg.type = GRPC_ARG_STRING;
server_host_override_arg.key = GRPC_SSL_TARGET_NAME_OVERRIDE_ARG;
server_host_override_arg.value.string = server_host_override;
channel_args.num_args = 1;
channel_args.args = &server_host_override_arg;
self->c_channel = grpc_secure_channel_create(
((ClientCredentials *)client_credentials)->c_client_credentials,
hostport, &channel_args);
}
return 0;
}
}
Expand Down
23 changes: 17 additions & 6 deletions src/python/src/grpc/_adapter/rear.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated):

def __init__(
self, host, port, pool, request_serializers, response_deserializers,
secure, root_certificates, private_key, certificate_chain):
secure, root_certificates, private_key, certificate_chain,
server_host_override=None):
"""Constructor.
Args:
Expand All @@ -111,6 +112,8 @@ def __init__(
key should be used.
certificate_chain: The PEM-encoded certificate chain to use or None if
no certificate chain should be used.
server_host_override: (For testing only) the target name used for SSL
host name checking.
"""
self._condition = threading.Condition()
self._host = host
Expand All @@ -132,6 +135,7 @@ def __init__(
self._root_certificates = root_certificates
self._private_key = private_key
self._certificate_chain = certificate_chain
self._server_host_override = server_host_override

def _on_write_event(self, operation_id, event, rpc_state):
if event.write_accepted:
Expand Down Expand Up @@ -327,7 +331,8 @@ def _start(self):
with self._condition:
self._completion_queue = _low.CompletionQueue()
self._channel = _low.Channel(
'%s:%d' % (self._host, self._port), self._client_credentials)
'%s:%d' % (self._host, self._port), self._client_credentials,
server_host_override=self._server_host_override)
return self

def _stop(self):
Expand Down Expand Up @@ -388,7 +393,8 @@ class _ActivatedRearLink(ticket_interfaces.RearLink, activated.Activated):

def __init__(
self, host, port, request_serializers, response_deserializers, secure,
root_certificates, private_key, certificate_chain):
root_certificates, private_key, certificate_chain,
server_host_override=None):
self._host = host
self._port = port
self._request_serializers = request_serializers
Expand All @@ -397,6 +403,7 @@ def __init__(
self._root_certificates = root_certificates
self._private_key = private_key
self._certificate_chain = certificate_chain
self._server_host_override = server_host_override

self._lock = threading.Lock()
self._pool = None
Expand All @@ -415,7 +422,8 @@ def _start(self):
self._rear_link = RearLink(
self._host, self._port, self._pool, self._request_serializers,
self._response_deserializers, self._secure, self._root_certificates,
self._private_key, self._certificate_chain)
self._private_key, self._certificate_chain,
server_host_override=self._server_host_override)
self._rear_link.join_fore_link(self._fore_link)
self._rear_link.start()
return self
Expand Down Expand Up @@ -477,7 +485,7 @@ def activated_rear_link(

def secure_activated_rear_link(
host, port, request_serializers, response_deserializers, root_certificates,
private_key, certificate_chain):
private_key, certificate_chain, server_host_override=None):
"""Creates a RearLink that is also an activated.Activated.
The returned object is only valid for use between calls to its start and stop
Expand All @@ -496,7 +504,10 @@ def secure_activated_rear_link(
should be used.
certificate_chain: The PEM-encoded certificate chain to use or None if no
certificate chain should be used.
server_host_override: (For testing only) the target name used for SSL
host name checking.
"""
return _ActivatedRearLink(
host, port, request_serializers, response_deserializers, True,
root_certificates, private_key, certificate_chain)
root_certificates, private_key, certificate_chain,
server_host_override=server_host_override)
Loading

0 comments on commit 3aca2a6

Please sign in to comment.