1 # -------------------------------------------------------------------------
2 # Copyright (c) 2015-2017 AT&T Intellectual Property
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 # -------------------------------------------------------------------------
20 OSDF Manager Main Flask Application
24 from optparse import OptionParser
30 from flask import Flask
32 from flask import request
33 from flask import Response
34 from onaplogging.mdcContext import MDC
35 from requests import RequestException
36 from schematics.exceptions import DataError
38 import osdf.adapters.aaf.sms as sms
39 from osdf.config.base import osdf_config
40 from osdf.logging.osdf_logging import audit_log
41 from osdf.logging.osdf_logging import debug_log
42 from osdf.logging.osdf_logging import error_log
43 from osdf.operation.error_handling import internal_error_message
44 from osdf.operation.error_handling import request_exception_to_json_body
45 from osdf.operation.exceptions import BusinessException
46 import osdf.operation.responses
47 from osdf.utils.mdc_utils import clear_mdc
48 from osdf.utils.mdc_utils import get_request_id
49 from osdf.utils.mdc_utils import populate_default_mdc
50 from osdf.utils.mdc_utils import populate_mdc
51 from osdf.utils.mdc_utils import set_error_details
53 ERROR_TEMPLATE = osdf.ERROR_TEMPLATE
57 BAD_CLIENT_REQUEST_MESSAGE = 'Client sent an invalid request'
60 @app.errorhandler(BusinessException)
61 def handle_business_exception(e):
62 """An exception explicitly raised due to some business rule
65 error_log.error("Synchronous error for request id {} {}"
66 .format(g.request_id, traceback.format_exc()))
67 err_msg = ERROR_TEMPLATE.render(description=str(e))
68 response = Response(err_msg, content_type='application/json; charset=utf-8')
69 response.status_code = 400
73 @app.errorhandler(RequestException)
74 def handle_request_exception(e):
75 """Returns a detailed synchronous message to the calling client when osdf fails due to a remote call to another system
78 error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
79 err_msg = request_exception_to_json_body(e)
80 response = Response(err_msg, content_type='application/json; charset=utf-8')
81 response.status_code = 400
85 @app.errorhandler(DataError)
86 def handle_data_error(e):
87 """Returns a detailed message to the calling client when the initial synchronous message is invalid
90 error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
94 "text": BAD_CLIENT_REQUEST_MESSAGE,
95 "exceptionMessage": str(e.errors),
96 "errorType": "InvalidClientRequest"
100 body_as_json = json.dumps(body_dictionary)
101 response = Response(body_as_json, content_type='application/json; charset=utf-8')
102 response.status_code = 400
109 if request.content_type and 'json' in request.content_type:
110 populate_mdc(request)
111 g.request_id = get_request_id(request.get_json())
112 log_message(json.dumps(request.get_json()), "INPROGRESS", 'ENTRY')
114 populate_default_mdc(request)
115 log_message('', "INPROGRESS", 'ENTRY')
119 def log_response(response):
120 log_response_data(response)
124 def log_response_data(response):
127 status_value = map_status_value(response)
128 log_message(response.get_data(as_text=True), status_value, 'EXIT')
131 set_default_audit_mdc(request, status_value, 'EXIT')
132 audit_log.info(response.get_data(as_text=True))
134 set_error_details(300, 'Internal Error')
135 error_log.error("Error logging the response data due to {}".format(traceback.format_exc()))
138 def set_default_audit_mdc(request, status_value, p_marker):
139 MDC.put('partnerName', 'internal')
140 MDC.put('serviceName', request.path)
141 MDC.put('statusCode', status_value)
142 MDC.put('requestID', 'internal')
143 MDC.put('timer', int((time.process_time() - g.request_start) * 1000))
144 MDC.put('customField1', p_marker)
147 def log_message(message, status_value, p_marker='INVOKE'):
148 MDC.put('statusCode', status_value)
149 MDC.put('customField1', p_marker)
150 MDC.put('timer', int((time.process_time() - g.request_start) * 1000))
151 audit_log.info(message)
154 def map_status_value(response):
155 if 200 <= response.status_code < 300:
156 status_value = "COMPLETE"
158 status_value = "ERROR"
162 @app.errorhandler(500)
163 def internal_failure(error):
164 """Returned when unexpected coding errors occur during initial synchronous processing
167 error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
168 response = Response(internal_error_message, content_type='application/json; charset=utf-8')
169 response.status_code = 500
173 def get_options(argv):
174 program_version_string = '%%prog %s' % "v1.0"
175 program_longdesc = ""
178 parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
179 parser.add_option("-l", "--local", dest="local", help="run locally", action="store_true", default=False)
180 parser.add_option("-t", "--devtest", dest="devtest", help="run in dev/test environment", action="store_true",
182 parser.add_option("-d", "--debughost", dest="debughost",
183 help="IP Address of host running debug server", default='')
184 parser.add_option("-p", "--debugport", dest="debugport",
185 help="Port number of debug server", type=int, default=5678)
186 opts, args = parser.parse_args(argv)
189 debug_log.debug('pydevd.settrace({}, port={})'.format(opts.debughost, opts.debugport))
190 # pydevd.settrace(opts.debughost, port=opts.debugport)
194 def build_ssl_context():
195 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
196 ssl_context.set_ciphers('ECDHE-RSA-AES128-SHA256:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH')
197 ssl_context.load_cert_chain(sys_conf['ssl_context'][0], sys_conf['ssl_context'][1])
203 sys_conf = osdf_config['core']['osdf_system']
204 ports = sys_conf['osdf_ports']
205 internal_port, external_port = ports['internal'], ports['external']
206 local_host = sys_conf['osdf_ip_default']
207 common_app_opts = dict(host=local_host, threaded=True, use_reloader=False)
208 ssl_opts = sys_conf.get('ssl_context')
210 common_app_opts.update({'ssl_context': build_ssl_context()})
211 opts = get_options(sys.argv)
212 # Load secrets from SMS
214 if not opts.local and not opts.devtest: # normal deployment
215 app.run(port=internal_port, debug=False, **common_app_opts)
217 port = internal_port if opts.local else external_port
218 app.run(port=port, debug=True, **common_app_opts)
221 if __name__ == "__main__":