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
36 from requests import RequestException
37 from schematics.exceptions import DataError
39 import osdf.adapters.aaf.sms as sms
40 from osdf.config.base import osdf_config
41 from osdf.logging.osdf_logging import audit_log
42 from osdf.logging.osdf_logging import debug_log
43 from osdf.logging.osdf_logging import error_log
44 from osdf.operation.error_handling import internal_error_message
45 from osdf.operation.error_handling import request_exception_to_json_body
46 from osdf.operation.exceptions import BusinessException
47 import osdf.operation.responses
48 from osdf.utils.mdc_utils import clear_mdc
49 from osdf.utils.mdc_utils import get_request_id
50 from osdf.utils.mdc_utils import populate_default_mdc
51 from osdf.utils.mdc_utils import populate_mdc
52 from osdf.utils.mdc_utils import set_error_details
54 ERROR_TEMPLATE = osdf.ERROR_TEMPLATE
58 BAD_CLIENT_REQUEST_MESSAGE = 'Client sent an invalid request'
61 @app.errorhandler(BusinessException)
62 def handle_business_exception(e):
63 """An exception explicitly raised due to some business rule
66 error_log.error("Synchronous error for request id {} {}"
67 .format(g.request_id, traceback.format_exc()))
68 err_msg = ERROR_TEMPLATE.render(description=str(e))
69 response = Response(err_msg, content_type='application/json; charset=utf-8')
70 response.status_code = 400
74 @app.errorhandler(RequestException)
75 def handle_request_exception(e):
76 """Returns a detailed synchronous message to the calling client when osdf fails due to a remote call to another system
79 error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
80 err_msg = request_exception_to_json_body(e)
81 response = Response(err_msg, content_type='application/json; charset=utf-8')
82 response.status_code = 400
86 @app.errorhandler(DataError)
87 def handle_data_error(e):
88 """Returns a detailed message to the calling client when the initial synchronous message is invalid
91 error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
95 "text": BAD_CLIENT_REQUEST_MESSAGE,
96 "exceptionMessage": str(e.errors),
97 "errorType": "InvalidClientRequest"
101 body_as_json = json.dumps(body_dictionary)
102 response = Response(body_as_json, content_type='application/json; charset=utf-8')
103 response.status_code = 400
110 if request.content_type and 'json' in request.content_type:
111 populate_mdc(request)
112 g.request_id = get_request_id(request.get_json())
113 log_message(json.dumps(request.get_json()), "INPROGRESS", 'ENTRY')
115 populate_default_mdc(request)
116 log_message('', "INPROGRESS", 'ENTRY')
120 def log_response(response):
121 log_response_data(response)
125 def log_response_data(response):
128 status_value = map_status_value(response)
129 log_message(response.get_data(as_text=True), status_value, 'EXIT')
132 set_default_audit_mdc(request, status_value, 'EXIT')
133 audit_log.info(response.get_data(as_text=True))
135 set_error_details(300, 'Internal Error')
136 error_log.error("Error logging the response data due to {}".format(traceback.format_exc()))
139 def set_default_audit_mdc(request, status_value, p_marker):
140 MDC.put('partnerName', 'internal')
141 MDC.put('serviceName', request.path)
142 MDC.put('statusCode', status_value)
143 MDC.put('requestID', 'internal')
144 MDC.put('timer', int((time.process_time() - g.request_start) * 1000))
145 MDC.put('customField1', p_marker)
148 def log_message(message, status_value, p_marker='INVOKE'):
149 MDC.put('statusCode', status_value)
150 MDC.put('customField1', p_marker)
151 MDC.put('timer', int((time.process_time() - g.request_start) * 1000))
152 audit_log.info(message)
155 def map_status_value(response):
156 if 200 <= response.status_code < 300:
157 status_value = "COMPLETE"
159 status_value = "ERROR"
163 @app.errorhandler(500)
164 def internal_failure(error):
165 """Returned when unexpected coding errors occur during initial synchronous processing
168 error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
169 response = Response(internal_error_message, content_type='application/json; charset=utf-8')
170 response.status_code = 500
174 def get_options(argv):
175 program_version_string = '%%prog %s' % "v1.0"
176 program_longdesc = ""
179 parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
180 parser.add_option("-l", "--local", dest="local", help="run locally", action="store_true", default=False)
181 parser.add_option("-t", "--devtest", dest="devtest", help="run in dev/test environment", action="store_true",
183 parser.add_option("-d", "--debughost", dest="debughost",
184 help="IP Address of host running debug server", default='')
185 parser.add_option("-p", "--debugport", dest="debugport",
186 help="Port number of debug server", type=int, default=5678)
187 opts, args = parser.parse_args(argv)
190 debug_log.debug('pydevd.settrace({}, port={})'.format(opts.debughost, opts.debugport))
191 pydevd.settrace(opts.debughost, port=opts.debugport)
195 def build_ssl_context():
196 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
197 ssl_context.set_ciphers('ECDHE-RSA-AES128-SHA256:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH')
198 ssl_context.load_cert_chain(sys_conf['ssl_context'][0], sys_conf['ssl_context'][1])
204 sys_conf = osdf_config['core']['osdf_system']
205 ports = sys_conf['osdf_ports']
206 internal_port, external_port = ports['internal'], ports['external']
207 local_host = sys_conf['osdf_ip_default']
208 common_app_opts = dict(host=local_host, threaded=True, use_reloader=False)
209 ssl_opts = sys_conf.get('ssl_context')
211 common_app_opts.update({'ssl_context': build_ssl_context()})
212 opts = get_options(sys.argv)
213 # Load secrets from SMS
215 if not opts.local and not opts.devtest: # normal deployment
216 app.run(port=internal_port, debug=False, **common_app_opts)
218 port = internal_port if opts.local else external_port
219 app.run(port=port, debug=True, **common_app_opts)
222 if __name__ == "__main__":