Skip to content

Commit

Permalink
Merge pull request grpc#830 from nathanielmanistaatgoogle/python-inte…
Browse files Browse the repository at this point in the history
…rop-client

The Python interop client.
  • Loading branch information
soltanmm committed Feb 26, 2015
2 parents e07edc5 + c2b4020 commit bc53734
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 9 deletions.
86 changes: 86 additions & 0 deletions src/python/interop/interop/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 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.

"""The Python implementation of the GRPC interoperability test client."""

import argparse

from grpc.early_adopter import implementations

from interop import methods
from interop import resources

_ONE_DAY_IN_SECONDS = 60 * 60 * 24


def _args():
parser = argparse.ArgumentParser()
parser.add_argument(
'--server_host', help='the host to which to connect', type=str)
parser.add_argument(
'--server_host_override',
help='the server host to which to claim to connect', type=str)
parser.add_argument(
'--server_port', help='the port to which to connect', type=int)
parser.add_argument(
'--test_case', help='the test case to execute', type=str)
parser.add_argument(
'--use_tls', help='require a secure connection', dest='use_tls',
action='store_true')
parser.add_argument(
'--use_test_ca', help='replace platform root CAs with ca.pem',
action='store_true')
return parser.parse_args()


def _stub(args):
if args.use_tls:
if args.use_test_ca:
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)
else:
stub = implementations.insecure_stub(
methods.CLIENT_METHODS, args.server_host, args.server_port)
return stub


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


if __name__ == '__main__':
_test_interoperability()
15 changes: 15 additions & 0 deletions src/python/interop/interop/credentials/ca.pem
Original file line number Diff line number Diff line change
@@ -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-----
136 changes: 136 additions & 0 deletions src/python/interop/interop/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,16 @@

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

import threading

from grpc.early_adopter import utilities

from interop import empty_pb2
from interop import messages_pb2

_TIMEOUT = 7


def _empty_call(request, unused_context):
return empty_pb2.Empty()

Expand Down Expand Up @@ -142,3 +147,134 @@ def _full_duplex_call(request_iterator, unused_context):
FULL_DUPLEX_CALL_METHOD_NAME: _SERVER_FULL_DUPLEX_CALL,
HALF_DUPLEX_CALL_METHOD_NAME: _SERVER_HALF_DUPLEX_CALL,
}


def _empty_unary(stub):
with stub:
response = stub.EmptyCall(empty_pb2.Empty(), _TIMEOUT)
if not isinstance(response, empty_pb2.Empty):
raise TypeError(
'response is of type "%s", not empty_pb2.Empty!', type(response))


def _large_unary(stub):
with stub:
request = messages_pb2.SimpleRequest(
response_type=messages_pb2.COMPRESSABLE, response_size=314159,
payload=messages_pb2.Payload(body=b'\x00' * 271828))
response_future = stub.UnaryCall.async(request, _TIMEOUT)
response = response_future.result()
if response.payload.type is not messages_pb2.COMPRESSABLE:
raise ValueError(
'response payload type is "%s"!' % type(response.payload.type))
if len(response.payload.body) != 314159:
raise ValueError(
'response body of incorrect size %d!' % len(response.payload.body))


def _client_streaming(stub):
with stub:
payload_body_sizes = (27182, 8, 1828, 45904)
payloads = (
messages_pb2.Payload(body=b'\x00' * size)
for size in payload_body_sizes)
requests = (
messages_pb2.StreamingInputCallRequest(payload=payload)
for payload in payloads)
response = stub.StreamingInputCall(requests, _TIMEOUT)
if response.aggregated_payload_size != 74922:
raise ValueError(
'incorrect size %d!' % response.aggregated_payload_size)


def _server_streaming(stub):
sizes = (31415, 9, 2653, 58979)

with stub:
request = messages_pb2.StreamingOutputCallRequest(
response_type=messages_pb2.COMPRESSABLE,
response_parameters=(
messages_pb2.ResponseParameters(size=sizes[0]),
messages_pb2.ResponseParameters(size=sizes[1]),
messages_pb2.ResponseParameters(size=sizes[2]),
messages_pb2.ResponseParameters(size=sizes[3]),
))
response_iterator = stub.StreamingOutputCall(request, _TIMEOUT)
for index, response in enumerate(response_iterator):
if response.payload.type != messages_pb2.COMPRESSABLE:
raise ValueError(
'response body of invalid type %s!' % response.payload.type)
if len(response.payload.body) != sizes[index]:
raise ValueError(
'response body of invalid size %d!' % len(response.payload.body))


class _Pipe(object):

def __init__(self):
self._condition = threading.Condition()
self._values = []
self._open = True

def __iter__(self):
return self

def next(self):
with self._condition:
while not self._values and self._open:
self._condition.wait()
if self._values:
return self._values.pop(0)
else:
raise StopIteration()

def add(self, value):
with self._condition:
self._values.append(value)
self._condition.notify()

def close(self):
with self._condition:
self._open = False
self._condition.notify()


def _ping_pong(stub):
request_response_sizes = (31415, 9, 2653, 58979)
request_payload_sizes = (27182, 8, 1828, 45904)

with stub:
pipe = _Pipe()
response_iterator = stub.FullDuplexCall(pipe, _TIMEOUT)
print 'Starting ping-pong with response iterator %s' % response_iterator
for response_size, payload_size in zip(
request_response_sizes, request_payload_sizes):
request = messages_pb2.StreamingOutputCallRequest(
response_type=messages_pb2.COMPRESSABLE,
response_parameters=(messages_pb2.ResponseParameters(
size=response_size),),
payload=messages_pb2.Payload(body=b'\x00' * payload_size))
pipe.add(request)
response = next(response_iterator)
if response.payload.type != messages_pb2.COMPRESSABLE:
raise ValueError(
'response body of invalid type %s!' % response.payload.type)
if len(response.payload.body) != response_size:
raise ValueError(
'response body of invalid size %d!' % len(response.payload.body))
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!')
56 changes: 56 additions & 0 deletions src/python/interop/interop/resources.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.

"""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)
11 changes: 3 additions & 8 deletions src/python/interop/interop/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,15 @@

import argparse
import logging
import pkg_resources
import time

from grpc.early_adopter import implementations

from interop import methods
from interop import resources

_ONE_DAY_IN_SECONDS = 60 * 60 * 24

_PRIVATE_KEY_RESOURCE_PATH = 'credentials/server1.key'
_CERTIFICATE_CHAIN_RESOURCE_PATH = 'credentials/server1.pem'


def serve():
parser = argparse.ArgumentParser()
Expand All @@ -54,10 +51,8 @@ def serve():
args = parser.parse_args()

if args.use_tls:
private_key = pkg_resources.resource_string(
__name__, _PRIVATE_KEY_RESOURCE_PATH)
certificate_chain = pkg_resources.resource_string(
__name__, _CERTIFICATE_CHAIN_RESOURCE_PATH)
private_key = resources.private_key()
certificate_chain = resources.certificate_chain()
server = implementations.secure_server(
methods.SERVER_METHODS, args.port, private_key, certificate_chain)
else:
Expand Down
4 changes: 3 additions & 1 deletion src/python/interop/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
}

_PACKAGE_DATA = {
'interop': ['credentials/server1.key', 'credentials/server1.pem',]
'interop': [
'credentials/ca.pem', 'credentials/server1.key',
'credentials/server1.pem',]
}

_INSTALL_REQUIRES = ['grpc-2015>=0.0.1']
Expand Down

0 comments on commit bc53734

Please sign in to comment.