Renaming Files having BluePrint to have Blueprint
[ccsdk/cds.git] / ms / artifact-manager / manager / servicer.py
1 """Copyright 2019 Deutsche Telekom.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7     http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 """
15 import socket
16 from datetime import datetime, timezone
17 from functools import wraps
18 from logging import Logger
19 from typing import NoReturn, Union
20
21 from grpc import ServicerContext
22 from manager.configuration import get_logger
23 from manager.errors import ArtifactManagerError, InvalidRequestError
24 from manager.utils import Repository, RepositoryStrategy
25 from onaplogging.mdcContext import MDC
26 from proto.BlueprintManagement_pb2 import (
27     BlueprintDownloadInput,
28     BlueprintManagementOutput,
29     BlueprintRemoveInput,
30     BlueprintUploadInput,
31 )
32 from proto.BlueprintManagement_pb2_grpc import BlueprintManagementServiceServicer
33
34 MDC_DATETIME_FORMAT = r"%Y-%m-%dT%H:%M:%S.%f%z"
35 COMMON_HEADER_DATETIME_FORMAT = r"%Y-%m-%dT%H:%M:%S.%fZ"
36
37
38 def 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",
50         request: Union[BlueprintDownloadInput, BlueprintRemoveInput, BlueprintUploadInput],
51         context: ServicerContext,
52     ) -> BlueprintManagementOutput:
53
54         if not all([request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion]):
55             raise InvalidRequestError("Request has to have set both Blueprint name and version")
56         output: BlueprintManagementOutput = func(servicer, request, context)
57         # 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
65 def 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",
75         request: Union[BlueprintDownloadInput, BlueprintRemoveInput, BlueprintUploadInput],
76         context: ServicerContext,
77     ) -> BlueprintManagementOutput:
78         try:
79             output: BlueprintManagementOutput = func(servicer, request, context)
80             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.
87             output: BlueprintManagementOutput = BlueprintManagementOutput()
88             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
105 def 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",
115         request: Union[BlueprintDownloadInput, BlueprintRemoveInput, BlueprintUploadInput],
116         context: ServicerContext,
117     ) -> BlueprintManagementOutput:
118         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
132         output: BlueprintManagementOutput = func(servicer, request, context)
133         MDC.clear()
134         return output
135
136     return _decorator
137
138
139 class ArtifactManagerServicer(BlueprintManagementServiceServicer):
140     """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
172     def downloadBlueprint(self, request: BlueprintDownloadInput, context: ServicerContext) -> BlueprintManagementOutput:
173         """Download blueprint file request method.
174
175         Currently it only logs when is called and all base class method.
176         :param request: BlueprintDownloadInput
177         :param context: ServicerContext
178         :return: BlueprintManagementOutput
179         """
180         output: BlueprintManagementOutput = BlueprintManagementOutput()
181         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
196     def uploadBlueprint(self, request: BlueprintUploadInput, context: ServicerContext) -> BlueprintManagementOutput:
197         """Upload blueprint file request method.
198
199         Currently it only logs when is called and all base class method.
200         :param request: BlueprintUploadInput
201         :param context: ServicerContext
202         :return: BlueprintManagementOutput
203         """
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         )
214         return BlueprintManagementOutput()
215
216     @prepare_logging_context
217     @translate_exception_to_response
218     @fill_common_header
219     def removeBlueprint(self, request: BlueprintRemoveInput, context: ServicerContext) -> BlueprintManagementOutput:
220         """Remove blueprint file request method.
221
222         Currently it only logs when is called and all base class method.
223         :param request: BlueprintRemoveInput
224         :param context: ServicerContext
225         :return: BlueprintManagementOutput
226         """
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         )
237         return BlueprintManagementOutput()