From 5064cb8bc7c7890283f810fbe27b284e5581c641 Mon Sep 17 00:00:00 2001 From: Marek Szwalkiewicz Date: Thu, 23 Jan 2020 15:27:26 +0000 Subject: Add resource resolution gRPC client. Adds a python module that contains new resource resolution client that should replace previous helpers done in Jython. Issue-ID: CCSDK-1989 Signed-off-by: Marek Szwalkiewicz Change-Id: I48b22acdc7fec31f28de84232c5b6b37124a0c2a --- ms/py-executor/.coveragerc | 2 + ms/py-executor/resource_resolution/README | 80 ++++++++++++++++++ ms/py-executor/resource_resolution/__init__.py | 14 ++++ ms/py-executor/resource_resolution/client.py | 94 ++++++++++++++++++++++ .../resource_resolution/tests/__init__.py | 14 ++++ .../resource_resolution/tests/client_test.py | 28 +++++++ ms/py-executor/test-requirements.txt | 3 + ms/py-executor/tox.ini | 25 ++++++ 8 files changed, 260 insertions(+) create mode 100644 ms/py-executor/.coveragerc create mode 100644 ms/py-executor/resource_resolution/README create mode 100644 ms/py-executor/resource_resolution/__init__.py create mode 100644 ms/py-executor/resource_resolution/client.py create mode 100644 ms/py-executor/resource_resolution/tests/__init__.py create mode 100644 ms/py-executor/resource_resolution/tests/client_test.py create mode 100644 ms/py-executor/test-requirements.txt create mode 100644 ms/py-executor/tox.ini (limited to 'ms/py-executor') diff --git a/ms/py-executor/.coveragerc b/ms/py-executor/.coveragerc new file mode 100644 index 000000000..593c2ac97 --- /dev/null +++ b/ms/py-executor/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = **/proto/* \ No newline at end of file diff --git a/ms/py-executor/resource_resolution/README b/ms/py-executor/resource_resolution/README new file mode 100644 index 000000000..a2d1542ab --- /dev/null +++ b/ms/py-executor/resource_resolution/README @@ -0,0 +1,80 @@ +# Resource resolution client + +## How to use examples + +### Insecure channel + +``` +from blueprints_grpc.proto.BluePrintCommon_pb2_grpc import ActionIdentifiers, CommonHeader +from blueprints_grpc.proto.BluePrintProcessing_pb2_grpc import ExecutionServiceInput +from resource_resolution.client import Client as ResourceResolutionClient + + +def generate_messages(): + commonHeader = CommonHeader() + commonHeader.requestId = "1234" + commonHeader.subRequestId = "1234-1" + commonHeader.originatorId = "CDS" + + actionIdentifiers = ActionIdentifiers() + actionIdentifiers.blueprintName = "sample-cba" + actionIdentifiers.blueprintVersion = "1.0.0" + actionIdentifiers.actionName = "SampleScript" + + input = ExecutionServiceInput(commonHeader=commonHeader, actionIdentifiers=actionIdentifiers) + + commonHeader2 = CommonHeader() + commonHeader2.requestId = "1235" + commonHeader2.subRequestId = "1234-2" + commonHeader2.originatorId = "CDS" + + input2 = ExecutionServiceInput(commonHeader=commonHeader2, actionIdentifiers=actionIdentifiers) + + yield from [input, input2] + + +if __name__ == "__main__": + with ResourceResolutionClient("localhost:50052") as client: + for response in client.process(generate_messages()): + print(response) + +``` + +### Secure channel + +``` +from blueprints_grpc.proto.BluePrintCommon_pb2_grpc import ActionIdentifiers, CommonHeader +from blueprints_grpc.proto.BluePrintProcessing_pb2_grpc import ExecutionServiceInput +from resource_resolution.client import Client as ResourceResolutionClient + + +def generate_messages(): + commonHeader = CommonHeader() + commonHeader.requestId = "1234" + commonHeader.subRequestId = "1234-1" + commonHeader.originatorId = "CDS" + + actionIdentifiers = ActionIdentifiers() + actionIdentifiers.blueprintName = "sample-cba" + actionIdentifiers.blueprintVersion = "1.0.0" + actionIdentifiers.actionName = "SampleScript" + + input = ExecutionServiceInput(commonHeader=commonHeader, actionIdentifiers=actionIdentifiers) + + commonHeader2 = CommonHeader() + commonHeader2.requestId = "1235" + commonHeader2.subRequestId = "1234-2" + commonHeader2.originatorId = "CDS" + + input2 = ExecutionServiceInput(commonHeader=commonHeader2, actionIdentifiers=actionIdentifiers) + + yield from [input, input2] + + +if __name__ == "__main__": + with open("certs/py-executor/py-executor-chain.pem", "rb") as f: + with ResourceResolutionClient("localhost:50052", use_ssl=True, root_certificates=f.read()) as client: + for response in client.process(generate_messages()): + print(response) + +``` \ No newline at end of file diff --git a/ms/py-executor/resource_resolution/__init__.py b/ms/py-executor/resource_resolution/__init__.py new file mode 100644 index 000000000..21236908e --- /dev/null +++ b/ms/py-executor/resource_resolution/__init__.py @@ -0,0 +1,14 @@ +"""Copyright 2019 Deutsche Telekom. + +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/ms/py-executor/resource_resolution/client.py b/ms/py-executor/resource_resolution/client.py new file mode 100644 index 000000000..913b0ed66 --- /dev/null +++ b/ms/py-executor/resource_resolution/client.py @@ -0,0 +1,94 @@ +"""Copyright 2019 Deutsche Telekom. + +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. +""" +from logging import getLogger, Logger +from types import TracebackType +from typing import Iterable, List, Optional, Type + +from grpc import Channel, insecure_channel, secure_channel, ssl_channel_credentials + +from blueprints_grpc.proto.BluePrintProcessing_pb2 import ExecutionServiceInput, ExecutionServiceOutput +from blueprints_grpc.proto.BluePrintProcessing_pb2_grpc import BluePrintProcessingServiceStub + + +class Client: + """Resource resoulution client class.""" + + def __init__( + self, + server_address: str, + *, + use_ssl: bool = False, + root_certificates: bytes = None, + private_key: bytes = None, + certificate_chain: bytes = None, + ) -> None: + """Client class initialization. + + :param server_address: Address to server to connect. + :param use_ssl: Boolean flag to determine if secure channel should be created or not. Keyword argument. + :param root_certificates: The PEM-encoded root certificates. None if it shouldn't be used. Keyword argument. + :param private_key: The PEM-encoded private key as a byte string, or None if no private key should be used. Keyword argument. + :param certificate_chain: The PEM-encoded certificate chain as a byte string to use or or None if no certificate chain should be used. Keyword argument. + """ + self.logger = getLogger(__name__) + if use_ssl: + self.channel: Channel = secure_channel( + server_address, ssl_channel_credentials(root_certificates, private_key, certificate_chain) + ) + self.logger.debug(f"Create secure channel to connect with {server_address}") + else: + self.channel: Channel = insecure_channel(server_address) + self.logger.debug(f"Create insecure channel to connect to {server_address}") + self.stub: BluePrintProcessingServiceStub = BluePrintProcessingServiceStub(self.channel) + + def close(self) -> None: + """Close client session. + + Closes client's channel. + """ + self.logger.debug("Close channel connection") + self.channel.close() + + def __enter__(self) -> Channel: + """Enter Client instance context. + + Return Client instance. In the context user can call methods to communicate with server. + On exit connection with the server is going to be closed. + """ + self.logger.debug("Enter Client instance context") + return self + + def __exit__( + self, + unused_exc_type: Optional[Type[BaseException]], + unused_exc_value: Optional[BaseException], + unused_traceback: Optional[TracebackType], + ) -> None: + """Exit Client instance context. + + Close connection with the server. + """ + self.logger.debug("Exit Client instance context") + self.close() + + def process(self, messages: Iterable[ExecutionServiceInput]) -> Iterable[ExecutionServiceOutput]: + """Send messages to server and return responses. + + :param messages: Iterable messages to send + :return: Iterable responses + """ + for message in self.stub.process(messages): + self.logger.debug(f"Get response message: {message}") + yield message diff --git a/ms/py-executor/resource_resolution/tests/__init__.py b/ms/py-executor/resource_resolution/tests/__init__.py new file mode 100644 index 000000000..21236908e --- /dev/null +++ b/ms/py-executor/resource_resolution/tests/__init__.py @@ -0,0 +1,14 @@ +"""Copyright 2019 Deutsche Telekom. + +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/ms/py-executor/resource_resolution/tests/client_test.py b/ms/py-executor/resource_resolution/tests/client_test.py new file mode 100644 index 000000000..2b94220f6 --- /dev/null +++ b/ms/py-executor/resource_resolution/tests/client_test.py @@ -0,0 +1,28 @@ +"""Copyright 2019 Deutsche Telekom. + +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. +""" + +from unittest.mock import MagicMock, patch + +from resource_resolution.client import Client + + +@patch("resource_resolution.client.insecure_channel") +def test_resource_resoulution_insecure_channel(insecure_channel_mock: MagicMock): + """Test if insecure_channel connection is called.""" + with patch.object(Client, "close") as client_close_method_mock: # Type MagicMock + with Client("127.0.0.1:3333"): + pass + insecure_channel_mock.called_once_with() + client_close_method_mock.called_once_with() diff --git a/ms/py-executor/test-requirements.txt b/ms/py-executor/test-requirements.txt new file mode 100644 index 000000000..79ed6ee95 --- /dev/null +++ b/ms/py-executor/test-requirements.txt @@ -0,0 +1,3 @@ +pytest==5.3.1 +pytest-grpc==0.7.0 +-r requirements.txt \ No newline at end of file diff --git a/ms/py-executor/tox.ini b/ms/py-executor/tox.ini new file mode 100644 index 000000000..8cf1776ba --- /dev/null +++ b/ms/py-executor/tox.ini @@ -0,0 +1,25 @@ +[tox] +envlist=py37,py38 +skipsdist=True +[testenv] +setenv = + CONFIGURATION = configuration-local.ini +deps = + -rtest-requirements.txt +commands = pytest resource_resolution/ +[testenv:codelint] +deps = + black +commands = black -l 120 --check {posargs:.} +[testenv:doclint] +deps = + flake8-docstrings +commands = flake8 --doctest --docstring-convention google --max-line-length 120 --exclude .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg,*test.py --select=D {posargs:.} +[testenv:coverage] +basepython = python3.7 +setenv = + CONFIGURATION = configuration-local.ini +deps = + -rtest-requirements.txt + pytest-cov +commands = pytest --cov=manager --cov=resource_resolution --cov-fail-under=60 --cov-config={toxinidir}/.coveragerc resource_resolution/ -- cgit 1.2.3-korg