blob: fd05fe0cc88e5ba36e1e382e137226bb744c53ba [file] [log] [blame]
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +00001"""Copyright 2019 Deutsche Telekom.
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License.
14"""
15import socket
16from datetime import datetime, timezone
17from functools import wraps
18from logging import Logger
19from typing import NoReturn, Union
20
21from grpc import ServicerContext
22from manager.configuration import get_logger
23from manager.errors import ArtifactManagerError, InvalidRequestError
24from manager.utils import Repository, RepositoryStrategy
25from onaplogging.mdcContext import MDC
KAPIL SINGALadcd4f22021-01-22 11:49:51 -050026from proto.BlueprintManagement_pb2 import (
27 BlueprintDownloadInput,
28 BlueprintManagementOutput,
29 BlueprintRemoveInput,
30 BlueprintUploadInput,
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +000031)
KAPIL SINGALadcd4f22021-01-22 11:49:51 -050032from proto.BlueprintManagement_pb2_grpc import BlueprintManagementServiceServicer
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +000033
34MDC_DATETIME_FORMAT = r"%Y-%m-%dT%H:%M:%S.%f%z"
35COMMON_HEADER_DATETIME_FORMAT = r"%Y-%m-%dT%H:%M:%S.%fZ"
36
37
38def fill_common_header(func):
39 """Decorator to fill handler's output values which is the same type for each handler.
40
41 It copies commonHeader from request object and set timestamp value.
42
43 :param func: Handler function
44 :return: _handler decorator callable object
45 """
46
47 @wraps(func)
48 def _decorator(
49 servicer: "ArtifactManagerServicer",
KAPIL SINGALadcd4f22021-01-22 11:49:51 -050050 request: Union[BlueprintDownloadInput, BlueprintRemoveInput, BlueprintUploadInput],
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +000051 context: ServicerContext,
KAPIL SINGALadcd4f22021-01-22 11:49:51 -050052 ) -> BlueprintManagementOutput:
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +000053
54 if not all([request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion]):
KAPIL SINGALadcd4f22021-01-22 11:49:51 -050055 raise InvalidRequestError("Request has to have set both Blueprint name and version")
56 output: BlueprintManagementOutput = func(servicer, request, context)
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +000057 # Set same values for every handler
58 output.commonHeader.CopyFrom(request.commonHeader)
59 output.commonHeader.timestamp = datetime.utcnow().strftime(COMMON_HEADER_DATETIME_FORMAT)
60 return output
61
62 return _decorator
63
64
65def translate_exception_to_response(func):
66 """Decorator that translates Artifact Manager exceptions into proper responses.
67
68 :param func: Handler function
69 :return: _handler decorator callable object
70 """
71
72 @wraps(func)
73 def _handler(
74 servicer: "ArtifactManagerServicer",
KAPIL SINGALadcd4f22021-01-22 11:49:51 -050075 request: Union[BlueprintDownloadInput, BlueprintRemoveInput, BlueprintUploadInput],
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +000076 context: ServicerContext,
KAPIL SINGALadcd4f22021-01-22 11:49:51 -050077 ) -> BlueprintManagementOutput:
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +000078 try:
KAPIL SINGALadcd4f22021-01-22 11:49:51 -050079 output: BlueprintManagementOutput = func(servicer, request, context)
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +000080 output.status.code = 200
81 output.status.message = "success"
82 except ArtifactManagerError as error:
83 # If ArtifactManagerError is raises one of defined error occurs.
84 # Every ArtifactManagerError based exception has status_code paramenter
85 # which has to be set in output. Use also exception's message to
86 # set errorMessage of the output.
KAPIL SINGALadcd4f22021-01-22 11:49:51 -050087 output: BlueprintManagementOutput = BlueprintManagementOutput()
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +000088 output.status.code = error.status_code
89 output.status.message = "failure"
90 output.status.errorMessage = str(error.message)
91
92 servicer.fill_MDC_timestamps()
93 servicer.logger.error(
94 "Error while processing the message - blueprintName={} blueprintVersion={}".format(
95 request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
96 ),
97 extra={"mdc": MDC.result()},
98 )
99 MDC.clear()
100 return output
101
102 return _handler
103
104
105def prepare_logging_context(func):
106 """Decorator that prepares MDC logging context for logs inside the handler.
107
108 :param func: Handler function
109 :return: _handler decorator callable object
110 """
111
112 @wraps(func)
113 def _decorator(
114 servicer: "ArtifactManagerServicer",
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500115 request: Union[BlueprintDownloadInput, BlueprintRemoveInput, BlueprintUploadInput],
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000116 context: ServicerContext,
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500117 ) -> BlueprintManagementOutput:
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000118 MDC.put("RequestID", request.commonHeader.requestId)
119 MDC.put("InvocationID", request.commonHeader.subRequestId)
120 MDC.put("ServiceName", servicer.__class__.__name__)
121 MDC.put("PartnerName", request.commonHeader.originatorId)
122 started_at = datetime.utcnow().replace(tzinfo=timezone.utc)
123 MDC.put("BeginTimestamp", started_at.strftime(MDC_DATETIME_FORMAT))
124
125 # Adding processing_started_at to the servicer so later we'll have the data to calculate elapsed time.
126 servicer.processing_started_at = started_at
127
128 MDC.put("TargetEntity", "py-executor")
129 MDC.put("TargetServiceName", func.__name__)
130 MDC.put("Server", socket.getfqdn())
131
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500132 output: BlueprintManagementOutput = func(servicer, request, context)
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000133 MDC.clear()
134 return output
135
136 return _decorator
137
138
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500139class ArtifactManagerServicer(BlueprintManagementServiceServicer):
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000140 """ArtifactManagerServer class.
141
142 Implements methods defined in proto files to manage artifacts repository.
143 These methods are: download, upload and remove.
144 """
145
146 processing_started_at = None
147
148 def __init__(self) -> NoReturn:
149 """Instance of ArtifactManagerServer class initialization.
150
151 Create logger for class using class name and set configuration property.
152 """
153 self.logger: Logger = get_logger(self.__class__.__name__)
154 self.repository: Repository = RepositoryStrategy.get_reporitory()
155
156 def fill_MDC_timestamps(self, status_code: int = 200) -> NoReturn:
157 """Add MDC context timestamps "in place".
158
159 :param status_code: int with expected response status. Default: 200 (success)
160 """
161 now = datetime.utcnow().replace(tzinfo=timezone.utc)
162 MDC.put("EndTimestamp", now.strftime(MDC_DATETIME_FORMAT))
163
164 # Elapsed time measured in miliseconds
165 MDC.put("ElapsedTime", (now - self.processing_started_at).total_seconds() * 1000)
166
167 MDC.put("StatusCode", status_code)
168
169 @prepare_logging_context
170 @translate_exception_to_response
171 @fill_common_header
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500172 def downloadBlueprint(self, request: BlueprintDownloadInput, context: ServicerContext) -> BlueprintManagementOutput:
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000173 """Download blueprint file request method.
174
175 Currently it only logs when is called and all base class method.
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500176 :param request: BlueprintDownloadInput
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000177 :param context: ServicerContext
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500178 :return: BlueprintManagementOutput
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000179 """
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500180 output: BlueprintManagementOutput = BlueprintManagementOutput()
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000181 output.fileChunk.chunk = self.repository.download_blueprint(
182 request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
183 )
184 self.fill_MDC_timestamps()
185 self.logger.info(
186 "Blueprint download successfuly processed - blueprintName={} blueprintVersion={}".format(
187 request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
188 ),
189 extra={"mdc": MDC.result()},
190 )
191 return output
192
193 @prepare_logging_context
194 @translate_exception_to_response
195 @fill_common_header
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500196 def uploadBlueprint(self, request: BlueprintUploadInput, context: ServicerContext) -> BlueprintManagementOutput:
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000197 """Upload blueprint file request method.
198
199 Currently it only logs when is called and all base class method.
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500200 :param request: BlueprintUploadInput
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000201 :param context: ServicerContext
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500202 :return: BlueprintManagementOutput
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000203 """
204 self.repository.upload_blueprint(
205 request.fileChunk.chunk, request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
206 )
207 self.fill_MDC_timestamps()
208 self.logger.info(
209 "Blueprint upload successfuly processed - blueprintName={} blueprintVersion={}".format(
210 request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
211 ),
212 extra={"mdc": MDC.result()},
213 )
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500214 return BlueprintManagementOutput()
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000215
216 @prepare_logging_context
217 @translate_exception_to_response
218 @fill_common_header
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500219 def removeBlueprint(self, request: BlueprintRemoveInput, context: ServicerContext) -> BlueprintManagementOutput:
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000220 """Remove blueprint file request method.
221
222 Currently it only logs when is called and all base class method.
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500223 :param request: BlueprintRemoveInput
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000224 :param context: ServicerContext
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500225 :return: BlueprintManagementOutput
Marek Szwalkiewiczbe4c4642020-01-30 13:49:18 +0000226 """
227 self.repository.remove_blueprint(
228 request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
229 )
230 self.fill_MDC_timestamps()
231 self.logger.info(
232 "Blueprint removal successfuly processed - blueprintName={} blueprintVersion={}".format(
233 request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion
234 ),
235 extra={"mdc": MDC.result()},
236 )
KAPIL SINGALadcd4f22021-01-22 11:49:51 -0500237 return BlueprintManagementOutput()