Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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 |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 26 | from proto.BlueprintManagement_pb2 import ( |
| 27 | BlueprintDownloadInput, |
| 28 | BlueprintManagementOutput, |
| 29 | BlueprintRemoveInput, |
| 30 | BlueprintUploadInput, |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 31 | ) |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 32 | from proto.BlueprintManagement_pb2_grpc import BlueprintManagementServiceServicer |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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", |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 50 | request: Union[BlueprintDownloadInput, BlueprintRemoveInput, BlueprintUploadInput], |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 51 | context: ServicerContext, |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 52 | ) -> BlueprintManagementOutput: |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 53 | |
| 54 | if not all([request.actionIdentifiers.blueprintName, request.actionIdentifiers.blueprintVersion]): |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 55 | raise InvalidRequestError("Request has to have set both Blueprint name and version") |
| 56 | output: BlueprintManagementOutput = func(servicer, request, context) |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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", |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 75 | request: Union[BlueprintDownloadInput, BlueprintRemoveInput, BlueprintUploadInput], |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 76 | context: ServicerContext, |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 77 | ) -> BlueprintManagementOutput: |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 78 | try: |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 79 | output: BlueprintManagementOutput = func(servicer, request, context) |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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. |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 87 | output: BlueprintManagementOutput = BlueprintManagementOutput() |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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", |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 115 | request: Union[BlueprintDownloadInput, BlueprintRemoveInput, BlueprintUploadInput], |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 116 | context: ServicerContext, |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 117 | ) -> BlueprintManagementOutput: |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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 | |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 132 | output: BlueprintManagementOutput = func(servicer, request, context) |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 133 | MDC.clear() |
| 134 | return output |
| 135 | |
| 136 | return _decorator |
| 137 | |
| 138 | |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 139 | class ArtifactManagerServicer(BlueprintManagementServiceServicer): |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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 |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 172 | def downloadBlueprint(self, request: BlueprintDownloadInput, context: ServicerContext) -> BlueprintManagementOutput: |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 173 | """Download blueprint file request method. |
| 174 | |
| 175 | Currently it only logs when is called and all base class method. |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 176 | :param request: BlueprintDownloadInput |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 177 | :param context: ServicerContext |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 178 | :return: BlueprintManagementOutput |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 179 | """ |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 180 | output: BlueprintManagementOutput = BlueprintManagementOutput() |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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 |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 196 | def uploadBlueprint(self, request: BlueprintUploadInput, context: ServicerContext) -> BlueprintManagementOutput: |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 197 | """Upload blueprint file request method. |
| 198 | |
| 199 | Currently it only logs when is called and all base class method. |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 200 | :param request: BlueprintUploadInput |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 201 | :param context: ServicerContext |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 202 | :return: BlueprintManagementOutput |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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 | ) |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 214 | return BlueprintManagementOutput() |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 215 | |
| 216 | @prepare_logging_context |
| 217 | @translate_exception_to_response |
| 218 | @fill_common_header |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 219 | def removeBlueprint(self, request: BlueprintRemoveInput, context: ServicerContext) -> BlueprintManagementOutput: |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 220 | """Remove blueprint file request method. |
| 221 | |
| 222 | Currently it only logs when is called and all base class method. |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 223 | :param request: BlueprintRemoveInput |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 224 | :param context: ServicerContext |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 225 | :return: BlueprintManagementOutput |
Marek Szwalkiewicz | be4c464 | 2020-01-30 13:49:18 +0000 | [diff] [blame] | 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 | ) |
KAPIL SINGAL | adcd4f2 | 2021-01-22 11:49:51 -0500 | [diff] [blame] | 237 | return BlueprintManagementOutput() |