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 <marek.szwalkiewicz@external.t-mobile.pl>
Change-Id: I48b22acdc7fec31f28de84232c5b6b37124a0c2a
diff --git a/ms/py-executor/.coveragerc b/ms/py-executor/.coveragerc
new file mode 100644
index 0000000..593c2ac
--- /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 0000000..a2d1542
--- /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 0000000..2123690
--- /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 0000000..913b0ed
--- /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 0000000..2123690
--- /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 0000000..2b94220
--- /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 0000000..79ed6ee
--- /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 0000000..8cf1776
--- /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/