summaryrefslogtreecommitdiffstats
path: root/ms/artifact-manager/manager/servicer.py
diff options
context:
space:
mode:
authorMarek Szwalkiewicz <marek.szwalkiewicz@external.t-mobile.pl>2020-01-30 13:49:18 +0000
committerMarek Szwalkiewicz <marek.szwalkiewicz@external.t-mobile.pl>2020-01-30 13:52:07 +0000
commitbe4c46420944531765ecc8bae7305086d71a36d0 (patch)
treebe9309a134a50e964b1257395d74c41c2da512ef /ms/artifact-manager/manager/servicer.py
parentda25e1649c9a10f998c8dde068641d7601a3f00a (diff)
Add Artifact Manager service.
Adds a micro service that offers gRPC interface for CBA artifacts manipulation. By default the service is attached to py-executor but can be ran as a separate service if needed in the future. Issue-ID: CCSDK-1988 Change-Id: I40e20f085ae1c1e81a48f76dbea181af28d9bd0d Signed-off-by: Marek Szwalkiewicz <marek.szwalkiewicz@external.t-mobile.pl>
Diffstat (limited to 'ms/artifact-manager/manager/servicer.py')
-rw-r--r--ms/artifact-manager/manager/servicer.py237
1 files changed, 237 insertions, 0 deletions
diff --git a/ms/artifact-manager/manager/servicer.py b/ms/artifact-manager/manager/servicer.py
new file mode 100644
index 000000000..be740b0e3
--- /dev/null
+++ b/ms/artifact-manager/manager/servicer.py
@@ -0,0 +1,237 @@
+"""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.
+"""
+import socket
+from datetime import datetime, timezone
+from functools import wraps
+from logging import Logger
+from typing import NoReturn, Union
+
+from grpc import ServicerContext
+from manager.configuration import get_logger
+from manager.errors import ArtifactManagerError, InvalidRequestError
+from manager.utils import Repository, RepositoryStrategy
+from onaplogging.mdcContext import MDC
+from proto.BluePrintManagement_pb2 import (
+ BluePrintDownloadInput,
+ BluePrintManagementOutput,
+ BluePrintRemoveInput,
+ BluePrintUploadInput,
+)
+from proto.BluePrintManagement_pb2_grpc import BluePrintManagementServiceServicer
+
+MDC_DATETIME_FORMAT = r"%Y-%m-%dT%H:%M:%S.%f%z"
+COMMON_HEADER_DATETIME_FORMAT = r"%Y-%m-%dT%H:%M:%S.%fZ"
+
+
+def fill_common_header(func):
+ """Decorator to fill handler's output values which is the same type for each handler.
+
+ It copies commonHeader from request object and set timestamp value.
+
+ :param func: Handler function
+ :return: _handler decorator callable object
+ """
+
+ @wraps(func)
+ def _decorator(
+ servicer: "ArtifactManagerServicer",
+ request: Union[BluePrintDownloadInput, BluePrintRemoveInput, BluePrintUploadInput],
+ context: ServicerContext,
+ ) -> BluePrintManagementOutput:
+
+ if not all([request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion]):
+ raise InvalidRequestError("Request has to have set both BluePrint name and version")
+ output: BluePrintManagementOutput = func(servicer, request, context)
+ # Set same values for every handler
+ output.commonHeader.CopyFrom(request.commonHeader)
+ output.commonHeader.timestamp = datetime.utcnow().strftime(COMMON_HEADER_DATETIME_FORMAT)
+ return output
+
+ return _decorator
+
+
+def translate_exception_to_response(func):
+ """Decorator that translates Artifact Manager exceptions into proper responses.
+
+ :param func: Handler function
+ :return: _handler decorator callable object
+ """
+
+ @wraps(func)
+ def _handler(
+ servicer: "ArtifactManagerServicer",
+ request: Union[BluePrintDownloadInput, BluePrintRemoveInput, BluePrintUploadInput],
+ context: ServicerContext,
+ ) -> BluePrintManagementOutput:
+ try:
+ output: BluePrintManagementOutput = func(servicer, request, context)
+ output.status.code = 200
+ output.status.message = "success"
+ except ArtifactManagerError as error:
+ # If ArtifactManagerError is raises one of defined error occurs.
+ # Every ArtifactManagerError based exception has status_code paramenter
+ # which has to be set in output. Use also exception's message to
+ # set errorMessage of the output.
+ output: BluePrintManagementOutput = BluePrintManagementOutput()
+ output.status.code = error.status_code
+ output.status.message = "failure"
+ output.status.errorMessage = str(error.message)
+
+ servicer.fill_MDC_timestamps()
+ servicer.logger.error(
+ "Error while processing the message - blueprintName={} blueprintVersion={}".format(
+ request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
+ ),
+ extra={"mdc": MDC.result()},
+ )
+ MDC.clear()
+ return output
+
+ return _handler
+
+
+def prepare_logging_context(func):
+ """Decorator that prepares MDC logging context for logs inside the handler.
+
+ :param func: Handler function
+ :return: _handler decorator callable object
+ """
+
+ @wraps(func)
+ def _decorator(
+ servicer: "ArtifactManagerServicer",
+ request: Union[BluePrintDownloadInput, BluePrintRemoveInput, BluePrintUploadInput],
+ context: ServicerContext,
+ ) -> BluePrintManagementOutput:
+ MDC.put("RequestID", request.commonHeader.requestId)
+ MDC.put("InvocationID", request.commonHeader.subRequestId)
+ MDC.put("ServiceName", servicer.__class__.__name__)
+ MDC.put("PartnerName", request.commonHeader.originatorId)
+ started_at = datetime.utcnow().replace(tzinfo=timezone.utc)
+ MDC.put("BeginTimestamp", started_at.strftime(MDC_DATETIME_FORMAT))
+
+ # Adding processing_started_at to the servicer so later we'll have the data to calculate elapsed time.
+ servicer.processing_started_at = started_at
+
+ MDC.put("TargetEntity", "py-executor")
+ MDC.put("TargetServiceName", func.__name__)
+ MDC.put("Server", socket.getfqdn())
+
+ output: BluePrintManagementOutput = func(servicer, request, context)
+ MDC.clear()
+ return output
+
+ return _decorator
+
+
+class ArtifactManagerServicer(BluePrintManagementServiceServicer):
+ """ArtifactManagerServer class.
+
+ Implements methods defined in proto files to manage artifacts repository.
+ These methods are: download, upload and remove.
+ """
+
+ processing_started_at = None
+
+ def __init__(self) -> NoReturn:
+ """Instance of ArtifactManagerServer class initialization.
+
+ Create logger for class using class name and set configuration property.
+ """
+ self.logger: Logger = get_logger(self.__class__.__name__)
+ self.repository: Repository = RepositoryStrategy.get_reporitory()
+
+ def fill_MDC_timestamps(self, status_code: int = 200) -> NoReturn:
+ """Add MDC context timestamps "in place".
+
+ :param status_code: int with expected response status. Default: 200 (success)
+ """
+ now = datetime.utcnow().replace(tzinfo=timezone.utc)
+ MDC.put("EndTimestamp", now.strftime(MDC_DATETIME_FORMAT))
+
+ # Elapsed time measured in miliseconds
+ MDC.put("ElapsedTime", (now - self.processing_started_at).total_seconds() * 1000)
+
+ MDC.put("StatusCode", status_code)
+
+ @prepare_logging_context
+ @translate_exception_to_response
+ @fill_common_header
+ def downloadBlueprint(self, request: BluePrintDownloadInput, context: ServicerContext) -> BluePrintManagementOutput:
+ """Download blueprint file request method.
+
+ Currently it only logs when is called and all base class method.
+ :param request: BluePrintDownloadInput
+ :param context: ServicerContext
+ :return: BluePrintManagementOutput
+ """
+ output: BluePrintManagementOutput = BluePrintManagementOutput()
+ output.fileChunk.chunk = self.repository.download_blueprint(
+ request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
+ )
+ self.fill_MDC_timestamps()
+ self.logger.info(
+ "Blueprint download successfuly processed - blueprintName={} blueprintVersion={}".format(
+ request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
+ ),
+ extra={"mdc": MDC.result()},
+ )
+ return output
+
+ @prepare_logging_context
+ @translate_exception_to_response
+ @fill_common_header
+ def uploadBlueprint(self, request: BluePrintUploadInput, context: ServicerContext) -> BluePrintManagementOutput:
+ """Upload blueprint file request method.
+
+ Currently it only logs when is called and all base class method.
+ :param request: BluePrintUploadInput
+ :param context: ServicerContext
+ :return: BluePrintManagementOutput
+ """
+ self.repository.upload_blueprint(
+ request.fileChunk.chunk, request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
+ )
+ self.fill_MDC_timestamps()
+ self.logger.info(
+ "Blueprint upload successfuly processed - blueprintName={} blueprintVersion={}".format(
+ request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
+ ),
+ extra={"mdc": MDC.result()},
+ )
+ return BluePrintManagementOutput()
+
+ @prepare_logging_context
+ @translate_exception_to_response
+ @fill_common_header
+ def removeBlueprint(self, request: BluePrintRemoveInput, context: ServicerContext) -> BluePrintManagementOutput:
+ """Remove blueprint file request method.
+
+ Currently it only logs when is called and all base class method.
+ :param request: BluePrintRemoveInput
+ :param context: ServicerContext
+ :return: BluePrintManagementOutput
+ """
+ self.repository.remove_blueprint(
+ request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
+ )
+ self.fill_MDC_timestamps()
+ self.logger.info(
+ "Blueprint removal successfuly processed - blueprintName={} blueprintVersion={}".format(
+ request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
+ ),
+ extra={"mdc": MDC.result()},
+ )
+ return BluePrintManagementOutput()